/* This file is part of the KDE libraries SPDX-FileCopyrightText: 2000 Matthias Hoelzer-Kluepfel SPDX-FileCopyrightText: 2001 Stephan Kulow SPDX-FileCopyrightText: 2003 Cornelius Schumacher SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "kio_help.h" #include "xslt_help.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KIO; QString HelpProtocol::langLookup(const QString &fname) { QStringList search; // assemble the local search paths const QStringList localDoc = KDocTools::documentationDirs(); QStringList langs = KLocalizedString::languages(); langs.append(QStringLiteral("en")); langs.removeAll(QStringLiteral("C")); auto shouldReplace = [](const QString &l) { return l == QLatin1String("en_US"); }; // this is kind of compat hack as we install our docs in en/ but the // default language is en_US std::replace_if(langs.begin(), langs.end(), shouldReplace, QStringLiteral("en")); // look up the different languages int ldCount = localDoc.count(); search.reserve(ldCount * langs.size()); for (int id = 0; id < ldCount; id++) { for (const QString &lang : std::as_const(langs)) { search.append(QStringLiteral("%1/%2/%3").arg(localDoc[id], lang, fname)); } } auto checkFile = [](const QString &str) { QFileInfo info(str); return info.exists() && info.isFile() && info.isReadable(); }; // try to locate the file for (const QString &path : std::as_const(search)) { // qDebug() << "Looking for help in: " << path; if (checkFile(path)) { return path; } if (path.endsWith(QLatin1String(".html"))) { const QString file = QStringView(path).left(path.lastIndexOf(QLatin1Char('/'))) + QLatin1String("/index.docbook"); // qDebug() << "Looking for help in: " << file; if (checkFile(file)) { return path; } } } return QString(); } QString HelpProtocol::lookupFile(const QString &fname, const QString &query, bool &redirect) { redirect = false; const QString &path = fname; QString result = langLookup(path); if (result.isEmpty()) { result = langLookup(path + QLatin1String("/index.html")); if (!result.isEmpty()) { QUrl red; red.setScheme(QStringLiteral("help")); red.setPath(path + QLatin1String("/index.html")); red.setQuery(query); redirection(red); // qDebug() << "redirect to " << red; redirect = true; } else { const QString documentationNotFound = QStringLiteral("kioworker6/help/documentationnotfound/index.html"); if (!langLookup(documentationNotFound).isEmpty()) { QUrl red; red.setScheme(QStringLiteral("help")); red.setPath(documentationNotFound); red.setQuery(query); redirection(red); redirect = true; } else { sendError(i18n("There is no documentation available for %1.", path.toHtmlEscaped())); return QString(); } } } else { // qDebug() << "result " << result; } return result; } void HelpProtocol::sendError(const QString &t) { data(QStringLiteral("\n%1") .arg(t.toHtmlEscaped()) .toUtf8()); } HelpProtocol::HelpProtocol(bool ghelp, const QByteArray &pool, const QByteArray &app) : WorkerBase(ghelp ? QByteArrayLiteral("ghelp") : QByteArrayLiteral("help"), pool, app) , mGhelp(ghelp) { } KIO::WorkerResult HelpProtocol::get(const QUrl &url) { ////qDebug() << "path=" << url.path() //<< "query=" << url.query(); bool redirect; QString doc = QDir::cleanPath(url.path()); if (doc.contains(QLatin1String(".."))) { return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toString()); } if (!mGhelp) { if (!doc.startsWith(QLatin1Char('/'))) { doc.prepend(QLatin1Char('/')); } if (doc.endsWith(QLatin1Char('/'))) { doc += QLatin1String("index.html"); } } infoMessage(i18n("Looking up correct file")); if (!mGhelp) { doc = lookupFile(doc, url.query(), redirect); if (redirect) { return KIO::WorkerResult::pass(); } } if (doc.isEmpty()) { return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toString()); } QUrl target; target.setPath(doc); if (url.hasFragment()) { target.setFragment(url.fragment()); } // qDebug() << "target " << target; QString file = target.isLocalFile() ? target.toLocalFile() : target.path(); if (mGhelp) { if (!file.endsWith(QLatin1String(".xml"))) { return get_file(file); } } else { const QString docbook_file = QStringView(file).left(file.lastIndexOf(QLatin1Char('/'))) + QLatin1String("/index.docbook"); if (!QFile::exists(file)) { file = docbook_file; } else { QFileInfo fi(file); if (fi.isDir()) { file += QLatin1String("/index.docbook"); } else { if (!file.endsWith(QLatin1String(".html")) || !compareTimeStamps(file, docbook_file)) { return get_file(file); } else { file = docbook_file; } } } } infoMessage(i18n("Preparing document")); mimeType(QStringLiteral("text/html")); if (mGhelp) { QString xsl = QStringLiteral("customization/kde-nochunk.xsl"); mParsed = KDocTools::transform(file, KDocTools::locateFileInDtdResource(xsl)); // qDebug() << "parsed " << mParsed.length(); if (mParsed.isEmpty()) { sendError(i18n("The requested help file could not be parsed:
%1", file)); } else { int pos1 = mParsed.indexOf(QLatin1String("charset=")); if (pos1 > 0) { int pos2 = mParsed.indexOf(QLatin1Char('"'), pos1); if (pos2 > 0) { mParsed.replace(pos1, pos2 - pos1, QStringLiteral("charset=UTF-8")); } } data(mParsed.toUtf8()); } } else { // qDebug() << "look for cache for " << file; mParsed = lookForCache(file); // qDebug() << "cached parsed " << mParsed.length(); if (mParsed.isEmpty()) { mParsed = KDocTools::transform(file, KDocTools::locateFileInDtdResource(QStringLiteral("customization/kde-chunk.xsl"))); if (!mParsed.isEmpty()) { infoMessage(i18n("Saving to cache")); #ifdef Q_OS_WIN QFileInfo fi(file); // make sure filenames do not contain the base path, otherwise // accessing user data from another location invalids cached files // Accessing user data under a different path is possible // when using usb sticks - this may affect unix/mac systems also const QString installPath = KDocTools::documentationDirs().last(); QString cache = QLatin1Char('/') + fi.absolutePath().remove(installPath, Qt::CaseInsensitive).replace(QLatin1Char('/'), QLatin1Char('_')) + QLatin1Char('_') + fi.baseName() + QLatin1Char('.'); #else QString cache = file.left(file.length() - 7); #endif KDocTools::saveToCache(mParsed, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/kio_help") + cache + QLatin1String("cache.bz2")); } } else { infoMessage(i18n("Using cached version")); } // qDebug() << "parsed " << mParsed.length(); if (mParsed.isEmpty()) { sendError(i18n("The requested help file could not be parsed:
%1", file)); } else { QString anchor; QString query = url.query(); // if we have a query, look if it contains an anchor if (!query.isEmpty()) { const QLatin1String anchorToken("?anchor="); if (query.startsWith(anchorToken)) { anchor = query.mid(anchorToken.size()).toLower(); QUrl redirURL(url); redirURL.setQuery(QString()); redirURL.setFragment(anchor); redirection(redirURL); return KIO::WorkerResult::pass(); } } if (anchor.isEmpty() && url.hasFragment()) { anchor = url.fragment(); } // qDebug() << "anchor: " << anchor; if (!anchor.isEmpty()) { int index = 0; while (true) { index = mParsed.indexOf(QStringLiteral("").arg(anchor)) { index = mParsed.lastIndexOf(QLatin1String("