File indexing completed on 2024-04-21 04:57:30

0001 // SPDX-License-Identifier: MIT
0002 
0003 #include "info.h"
0004 
0005 #include <stdio.h>
0006 #include <stdlib.h>
0007 #include <sys/stat.h>
0008 #ifdef Q_OS_WIN
0009 #include <process.h>
0010 #define getpid _getpid
0011 #define popen _popen
0012 #define pclose _pclose
0013 #else
0014 #include <unistd.h> // getpid()
0015 #endif
0016 
0017 #include <QCoreApplication>
0018 #include <QFile>
0019 #include <QStandardPaths>
0020 #include <QUrl>
0021 
0022 #include <KLocalizedString>
0023 #include <KShell>
0024 
0025 using namespace KIO;
0026 
0027 // Pseudo plugin class to embed meta data
0028 class KIOPluginForMetaData : public QObject
0029 {
0030     Q_OBJECT
0031     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.info" FILE "info.json")
0032 };
0033 
0034 InfoProtocol::InfoProtocol(const QByteArray &pool, const QByteArray &app)
0035     : WorkerBase("info", pool, app)
0036 {
0037     qCDebug(LOG_KIO_INFO);
0038 
0039     m_cssLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_docfilter/kio_docfilter.css");
0040     if (m_cssLocation.isEmpty())
0041         m_missingFiles.append("kio_docfilter/kio_docfilter.css");
0042     m_perl = QStandardPaths::findExecutable("perl");
0043     if (m_perl.isEmpty())
0044         m_missingFiles.append("perl");
0045     m_infoScript = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_info/kde-info2html");
0046     if (m_infoScript.isEmpty())
0047         m_missingFiles.append("kio_info/kde-info2html");
0048     m_infoConf = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_info/kde-info2html.conf");
0049     if (m_infoConf.isEmpty())
0050         m_missingFiles.append("kio_info/kde-info2html.conf");
0051 
0052     if (!m_missingFiles.isEmpty()) {
0053         qCCritical(LOG_KIO_INFO) << "Cannot locate files for HTML conversion," << qPrintable(m_missingFiles.join(' '));
0054     }
0055 
0056     qCDebug(LOG_KIO_INFO) << "done";
0057 }
0058 
0059 KIO::WorkerResult InfoProtocol::missingFilesReult() const
0060 {
0061     const QString errorStr = i18n(
0062         "Unable to locate files which are necessary to run this service:<br>%1<br>"
0063         "Please check your software installation.",
0064         m_missingFiles.join(' '));
0065     return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, errorStr);
0066 }
0067 
0068 KIO::WorkerResult InfoProtocol::get(const QUrl &url)
0069 {
0070     qCDebug(LOG_KIO_INFO) << "URL" << url.toDisplayString() << "path" << url.path();
0071 
0072     if (!m_missingFiles.isEmpty()) {
0073         return missingFilesReult();
0074     }
0075 
0076     if (url.path() == "/") {
0077         QUrl newUrl("info:/dir");
0078         redirection(newUrl);
0079         return KIO::WorkerResult::pass();
0080         ;
0081     };
0082 
0083     // some people write info://autoconf instead of info:/autoconf
0084     if (!url.host().isEmpty()) {
0085         QUrl newURl(url);
0086         newURl.setPath(url.host() + url.path());
0087         newURl.setHost(QString());
0088         redirection(newURl);
0089         return KIO::WorkerResult::pass();
0090         ;
0091     }
0092 
0093     if (url.path().right(1) == "/") {
0094         // Trailing / are not supported, so we need to remove them.
0095         QUrl newUrl(url);
0096         QString newPath(url.path());
0097         newPath.chop(1);
0098         newUrl.setPath(newPath);
0099         redirection(newUrl);
0100         return KIO::WorkerResult::pass();
0101         ;
0102     }
0103 
0104     // '<' in the path looks suspicious, someone is trying info:/dir/<script>alert('xss')</script>
0105     if (url.path().contains('<')) {
0106         return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.url());
0107     }
0108 
0109     mimeType("text/html");
0110     // extract the path and node from url
0111     decodeURL(url);
0112 
0113     QString cmd = KShell::quoteArg(m_perl);
0114     cmd += ' ';
0115     cmd += KShell::quoteArg(m_infoScript);
0116     cmd += ' ';
0117     cmd += KShell::quoteArg(m_infoConf);
0118     cmd += ' ';
0119     cmd += KShell::quoteArg(m_cssLocation);
0120     cmd += ' ';
0121     cmd += KShell::quoteArg(m_page);
0122     cmd += ' ';
0123     cmd += KShell::quoteArg(m_node);
0124 
0125     qCDebug(LOG_KIO_INFO) << "cmd" << cmd;
0126 
0127     FILE *file = popen(QFile::encodeName(cmd).constData(), "r");
0128     if (!file) {
0129         qCDebug(LOG_KIO_INFO) << "popen failed";
0130         return KIO::WorkerResult::fail(ERR_CANNOT_LAUNCH_PROCESS, cmd);
0131     }
0132 
0133     char buffer[4096];
0134 
0135     bool empty = true;
0136     while (!feof(file)) {
0137         int n = fread(buffer, 1, sizeof(buffer), file);
0138         if (!n && feof(file) && empty) {
0139             return KIO::WorkerResult::fail(ERR_CANNOT_LAUNCH_PROCESS, cmd);
0140         }
0141         if (n < 0) {
0142             // ERROR
0143             qCWarning(LOG_KIO_INFO) << "read error!";
0144             pclose(file);
0145             return KIO::WorkerResult::fail();
0146         }
0147 
0148         empty = false;
0149         data(QByteArray::fromRawData(buffer, n));
0150     }
0151 
0152     pclose(file);
0153 
0154     qCDebug(LOG_KIO_INFO) << "done";
0155 
0156     return KIO::WorkerResult::pass();
0157 }
0158 
0159 KIO::WorkerResult InfoProtocol::mimetype(const QUrl & /* url */)
0160 {
0161     qCDebug(LOG_KIO_INFO);
0162 
0163     if (!m_missingFiles.isEmpty()) {
0164         return missingFilesReult();
0165     }
0166 
0167     // to get rid of those "Open with" dialogs...
0168     mimeType("text/html");
0169 
0170     // finish action
0171     return KIO::WorkerResult::pass();
0172 }
0173 
0174 void InfoProtocol::decodeURL(const QUrl &url)
0175 {
0176     qCDebug(LOG_KIO_INFO) << url;
0177 
0178     /* Notes:
0179      *
0180      * I cleaned up the URL decoding and chose not to support URLs in the
0181      * form "info:/usr/local/share/info/libc.info.gz" or similar which the
0182      * older code attempted (and failed, maybe it had worked once) to do.
0183      *
0184      * The reason is that an obvious use such as viewing a info file off your
0185      * infopath would work for the first page, but then all the links would be
0186      * wrong. Of course, one could change kde-info2html to make it work, but I don't
0187      * think it worthy, others are free to disagree and write the necessary code ;)
0188      *
0189      * luis pedro
0190      */
0191 
0192     if (url == QUrl("info:/browse_by_file?special=yes")) {
0193         m_page = "#special#";
0194         m_node = "browse_by_file";
0195         qCDebug(LOG_KIO_INFO) << "InfoProtocol::decodeURL - special - browse by file";
0196         return;
0197     }
0198 
0199     decodePath(url.path());
0200 }
0201 
0202 void InfoProtocol::decodePath(QString path)
0203 {
0204     qCDebug(LOG_KIO_INFO) << path;
0205 
0206     m_page = "dir"; // default
0207     m_node = "";
0208 
0209     // remove leading slash
0210     if ('/' == path[0]) {
0211         path = path.mid(1);
0212     }
0213     // qCDebug(LOG_KIO_INFO) << "Path: " << path;
0214 
0215     int slashPos = path.indexOf("/");
0216 
0217     if (slashPos < 0) {
0218         m_page = path;
0219         m_node = "Top";
0220         return;
0221     }
0222 
0223     m_page = path.left(slashPos);
0224 
0225     // remove leading+trailing whitespace
0226     m_node = path.right(path.length() - slashPos - 1).trimmed();
0227 
0228     qCDebug(LOG_KIO_INFO) << "-> page" << m_page << "node" << m_node;
0229 }
0230 
0231 // A minimalistic stat with only the file type
0232 // This seems to be enough for konqueror
0233 KIO::WorkerResult InfoProtocol::stat(const QUrl &)
0234 {
0235     if (!m_missingFiles.isEmpty()) {
0236         return missingFilesReult();
0237     }
0238 
0239     UDSEntry uds_entry;
0240 
0241 #ifdef Q_OS_WIN
0242     // Regular file with rwx permission for all
0243     uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0244 #else
0245     // Regular file with rwx permission for all
0246     uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO);
0247 #endif
0248 
0249     statEntry(uds_entry);
0250     return KIO::WorkerResult::pass();
0251 }
0252 
0253 extern "C" {
0254 int Q_DECL_EXPORT kdemain(int argc, char **argv);
0255 }
0256 
0257 int kdemain(int argc, char **argv)
0258 {
0259     QCoreApplication app(argc, argv); // needed for QSocketNotifier
0260     app.setApplicationName(QLatin1String("kio_info"));
0261 
0262     qCDebug(LOG_KIO_INFO) << "kio_info starting" << getpid();
0263 
0264     if (argc != 4) {
0265         fprintf(stderr, "Usage: kio_info protocol domain-socket1 domain-socket2\n");
0266         exit(-1);
0267     }
0268 
0269     InfoProtocol worker(argv[2], argv[3]);
0270     worker.dispatchLoop();
0271 
0272     return 0;
0273 }
0274 
0275 #include "info.moc"