diff --git a/filenamesearch/kio_filenamesearch.cpp b/filenamesearch/kio_filenamesearch.cpp index b5cfdfa8..2ffee969 100644 --- a/filenamesearch/kio_filenamesearch.cpp +++ b/filenamesearch/kio_filenamesearch.cpp @@ -1,184 +1,183 @@ /*************************************************************************** * Copyright (C) 2010 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kio_filenamesearch.h" #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(KIO_FILENAMESEARCH, "kio_filenamesearch") FileNameSearchProtocol::FileNameSearchProtocol(const QByteArray &pool, const QByteArray &app) : SlaveBase("search", pool, app) { QDBusInterface kded(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")); kded.call(QStringLiteral("loadModule"), QStringLiteral("filenamesearchmodule")); } FileNameSearchProtocol::~FileNameSearchProtocol() { } void FileNameSearchProtocol::listDir(const QUrl &url) { const QUrlQuery urlQuery(url); const QString search = urlQuery.queryItemValue("search"); if (search.isEmpty()) { finished(); return; } const QRegularExpression pattern(search, QRegularExpression::CaseInsensitiveOption); std::function validator; if (urlQuery.queryItemValue("checkContent") == QStringLiteral("yes")) { validator = [pattern](const KFileItem &item) -> bool { return item.determineMimeType().inherits(QStringLiteral("text/plain")) && contentContainsPattern(item.url(), pattern); }; } else { validator = [pattern](const KFileItem &item) -> bool { return item.text().contains(pattern); }; } QSet iteratedDirs; const QUrl directory(urlQuery.queryItemValue("url")); searchDirectory(directory, validator, iteratedDirs); finished(); } void FileNameSearchProtocol::searchDirectory(const QUrl &directory, const std::function &itemValidator, QSet &iteratedDirs) { if (directory.path() == QStringLiteral("/proc")) { // Don't try to iterate the /proc directory of Linux return; } // Get all items of the directory QScopedPointer dirLister(new KCoreDirLister); dirLister->setDelayedMimeTypes(true); dirLister->openUrl(directory); QEventLoop eventLoop; QObject::connect(dirLister.data(), static_cast(&KCoreDirLister::canceled), &eventLoop, &QEventLoop::quit); QObject::connect(dirLister.data(), static_cast(&KCoreDirLister::completed), &eventLoop, &QEventLoop::quit); eventLoop.exec(); // Visualize all items that match the search pattern QList pendingDirs; const KFileItemList items = dirLister->items(); for (const KFileItem &item : items) { if (itemValidator(item)) { KIO::UDSEntry entry = item.entry(); entry.replace(KIO::UDSEntry::UDS_URL, item.url().url()); listEntry(entry); } if (item.isDir()) { if (item.isLink()) { // Assure that no endless searching is done in directories that // have already been iterated. const QUrl linkDest = item.url().resolved(QUrl::fromLocalFile(item.linkDest())); if (!iteratedDirs.contains(linkDest.path())) { pendingDirs.append(linkDest); } } else { pendingDirs.append(item.url()); } } } iteratedDirs.insert(directory.path()); dirLister.reset(); // Recursively iterate all sub directories for (const QUrl &pendingDir : qAsConst(pendingDirs)) { searchDirectory(pendingDir, itemValidator, iteratedDirs); } } bool FileNameSearchProtocol::contentContainsPattern(const QUrl &fileName, const QRegularExpression &pattern) { auto fileContainsPattern = [&pattern](const QString &path) -> bool { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return false; } QTextStream in(&file); while (!in.atEnd()) { const QString line = in.readLine(); if (line.contains(pattern)) { return true; } } return false; }; if (fileName.isLocalFile()) { return fileContainsPattern(fileName.toLocalFile()); } else { QTemporaryFile tempFile; if (tempFile.open()) { KIO::Job* getJob = KIO::file_copy(fileName, QUrl::fromLocalFile(tempFile.fileName()), -1, KIO::Overwrite | KIO::HideProgressInfo); if (getJob->exec()) { // The non-local file was downloaded successfully. return fileContainsPattern(tempFile.fileName()); } } } return false; } extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); if (argc != 4) { - qCDebug(KIO_FILENAMESEARCH) << "Usage: kio_filenamesearch protocol domain-socket1 domain-socket2" - << endl; + qCDebug(KIO_FILENAMESEARCH) << "Usage: kio_filenamesearch protocol domain-socket1 domain-socket2"; return -1; } FileNameSearchProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } diff --git a/info/info.cc b/info/info.cc index c8bf340c..de4513e4 100644 --- a/info/info.cc +++ b/info/info.cc @@ -1,272 +1,272 @@ #include "info.h" #include #include #include #ifdef Q_OS_WIN #include #define getpid _getpid #define popen _popen #define pclose _pclose #else #include // getpid() #endif #include #include #include #include #include #include Q_LOGGING_CATEGORY(LOG_KIO_INFO, "kio_info") using namespace KIO; InfoProtocol::InfoProtocol( const QByteArray &pool, const QByteArray &app ) : SlaveBase( "info", pool, app ) , m_page( "" ) , m_node( "" ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::InfoProtocol"; m_cssLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_docfilter/kio_docfilter.css" ); m_perl = QStandardPaths::findExecutable( "perl" ); m_infoScript = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_info/kde-info2html" ); m_infoConf = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_info/kde-info2html.conf"); if( m_perl.isNull() || m_infoScript.isNull() || m_infoConf.isNull() ) { - qCCritical( LOG_KIO_INFO ) << "Critical error: Cannot locate files for HTML-conversion" << endl; + qCCritical( LOG_KIO_INFO ) << "Critical error: Cannot locate files for HTML-conversion"; QString errorStr; if ( m_perl.isNull() ) { errorStr = "perl."; } else { QString missing =m_infoScript.isNull() ? "kio_info/kde-info2html" : "kio_info/kde-info2html.conf"; errorStr = "kde-info2html" + i18n( "\nUnable to locate file %1 which is necessary to run this service. " "Please check your software installation." , missing ); } error( KIO::ERR_CANNOT_LAUNCH_PROCESS, errorStr ); exit(); } qCDebug( LOG_KIO_INFO ) << "InfoProtocol::InfoProtocol - done"; } InfoProtocol::~InfoProtocol() { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::~InfoProtocol"; qCDebug( LOG_KIO_INFO ) << "InfoProtocol::~InfoProtocol - done"; } void InfoProtocol::get( const QUrl& url ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::get"; qCDebug( LOG_KIO_INFO ) << "URL: " << url.toDisplayString() << " , Path :" << url.path(); if (url.path()=="/") { QUrl newUrl("info:/dir"); redirection(newUrl); finished(); return; }; // some people write info://autoconf instead of info:/autoconf if (!url.host().isEmpty()) { QUrl newURl(url); newURl.setPath(url.host()+url.path()); newURl.setHost(QString()); redirection(newURl); finished(); return; } if ( url.path().right(1) == "/" ) { // Trailing / are not supported, so we need to remove them. QUrl newUrl( url ); QString newPath( url.path() ); newPath.chop( 1 ); newUrl.setPath( newPath ); redirection( newUrl ); finished(); return; } // '<' in the path looks suspicious, someone is trying info:/dir/ if (url.path().contains('<')) { error(KIO::ERR_DOES_NOT_EXIST, url.url()); return; } mimeType("text/html"); // extract the path and node from url decodeURL( url ); QString cmd = KShell::quoteArg(m_perl); cmd += ' '; cmd += KShell::quoteArg(m_infoScript); cmd += ' '; cmd += KShell::quoteArg(m_infoConf); cmd += ' '; cmd += KShell::quoteArg(m_cssLocation); cmd += ' '; cmd += KShell::quoteArg(m_page); cmd += ' '; cmd += KShell::quoteArg(m_node); qCDebug( LOG_KIO_INFO ) << "cmd: " << cmd; FILE *file = popen( QFile::encodeName(cmd), "r" ); if ( !file ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::get popen failed"; error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); return; } char buffer[ 4096 ]; bool empty = true; while ( !feof( file ) ) { int n = fread( buffer, 1, sizeof( buffer ), file ); if ( !n && feof( file ) && empty ) { error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); return; } if ( n < 0 ) { // ERROR qCDebug( LOG_KIO_INFO ) << "InfoProtocol::get ERROR!"; pclose( file ); return; } empty = false; data( QByteArray::fromRawData( buffer, n ) ); } pclose( file ); finished(); qCDebug( LOG_KIO_INFO ) << "InfoProtocol::get - done"; } void InfoProtocol::mimetype( const QUrl& /* url */ ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::mimetype"; // to get rid of those "Open with" dialogs... mimeType( "text/html" ); // finish action finished(); qCDebug( LOG_KIO_INFO ) << "InfoProtocol::mimetype - done"; } void InfoProtocol::decodeURL( const QUrl &url ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::decodeURL"; /* Notes: * * I cleaned up the URL decoding and chose not to support URLs in the * form "info:/usr/local/share/info/libc.info.gz" or similar which the * older code attempted (and failed, maybe it had worked once) to do. * * The reason is that an obvious use such as viewing a info file off your * infopath would work for the first page, but then all the links would be * wrong. Of course, one could change kde-info2html to make it work, but I don't * think it worthy, others are free to disagree and write the necessary code ;) * * luis pedro */ if ( url == QUrl("info:/browse_by_file?special=yes") ) { m_page = "#special#"; m_node = "browse_by_file"; qCDebug( LOG_KIO_INFO ) << "InfoProtocol::decodeURL - special - browse by file"; return; } decodePath( url.path() ); qCDebug( LOG_KIO_INFO ) << "InfoProtocol::decodeURL - done"; } void InfoProtocol::decodePath( QString path ) { qCDebug( LOG_KIO_INFO ) << "InfoProtocol::decodePath(-" < This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kio_man.h" #include "kio_man_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "man2html.h" #include #include #include using namespace KIO; MANProtocol *MANProtocol::_self = nullptr; #define SGML2ROFF_DIRS "/usr/lib/sgml" /* * Drop trailing ".section[.gz]" from name */ static void stripExtension( QString *name ) { int pos = name->length(); if ( name->indexOf(".gz", -3) != -1 ) pos -= 3; else if ( name->indexOf(".z", -2, Qt::CaseInsensitive) != -1 ) pos -= 2; else if ( name->indexOf(".bz2", -4) != -1 ) pos -= 4; else if ( name->indexOf(".bz", -3) != -1 ) pos -= 3; else if ( name->indexOf(".lzma", -5) != -1 ) pos -= 5; else if ( name->indexOf(".xz", -3) != -1 ) pos -= 3; if ( pos > 0 ) pos = name->lastIndexOf('.', pos-1); if ( pos > 0 ) name->truncate( pos ); } static bool parseUrl(const QString& _url, QString &title, QString §ion) { section.clear(); QString url = _url; url = url.trimmed(); if (url.isEmpty() || url.at(0) == '/') { if (url.isEmpty() || QFile::exists(url)) { // man:/usr/share/man/man1/ls.1.gz is a valid file title = url; return true; } else { // If the directory does not exist, then it is perhaps a normal man page qCDebug(KIO_MAN_LOG) << url << " does not exist"; } } while (!url.isEmpty() && url.at(0) == '/') url.remove(0,1); title = url; int pos = url.indexOf('('); if (pos < 0) return true; // man:ls -> title=ls title = title.left(pos); section = url.mid(pos+1); pos = section.indexOf(')'); if (pos >= 0) { if (pos < section.length() - 2 && title.isEmpty()) { title = section.mid(pos + 2); } section = section.left(pos); } // man:ls(2) -> title="ls", section="2" return true; } MANProtocol::MANProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : QObject(), SlaveBase("man", pool_socket, app_socket) { assert(!_self); _self = this; section_names << "0" << "0p" << "1" << "1p" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7" << "8" << "9" << "l" << "n"; QString cssPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_docfilter/kio_docfilter.css" )); QUrl cssUrl(QUrl::fromLocalFile(cssPath)); m_manCSSFile = cssUrl.url().toUtf8(); } MANProtocol *MANProtocol::self() { return _self; } MANProtocol::~MANProtocol() { _self = nullptr; } void MANProtocol::parseWhatIs( QMap &i, QTextStream &t, const QString &mark ) { QRegExp re( mark ); QString l; while ( !t.atEnd() ) { l = t.readLine(); int pos = re.indexIn( l ); if (pos != -1) { QString names = l.left(pos); QString descr = l.mid(pos + re.matchedLength()); while ((pos = names.indexOf(",")) != -1) { i[names.left(pos++)] = descr; while (names[pos] == ' ') pos++; names = names.mid(pos); } i[names] = descr; } } } bool MANProtocol::addWhatIs(QMap &i, const QString &name, const QString &mark) { QFile f(name); if (!f.open(QIODevice::ReadOnly)) return false; QTextStream t(&f); parseWhatIs( i, t, mark ); return true; } QMap MANProtocol::buildIndexMap(const QString §ion) { QMap i; QStringList man_dirs = manDirectories(); // Supplementary places for whatis databases man_dirs += m_mandbpath; if (!man_dirs.contains("/var/cache/man")) man_dirs << "/var/cache/man"; if (!man_dirs.contains("/var/catman")) man_dirs << "/var/catman"; QStringList names; names << "whatis.db" << "whatis"; QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+"; for ( QStringList::ConstIterator it_dir = man_dirs.constBegin(); it_dir != man_dirs.constEnd(); ++it_dir ) { if ( QFile::exists( *it_dir ) ) { QStringList::ConstIterator it_name; for ( it_name = names.constBegin(); it_name != names.constEnd(); it_name++ ) { if (addWhatIs(i, (*it_dir) + '/' + (*it_name), mark)) break; } if ( it_name == names.constEnd() ) { QProcess proc; proc.setProgram("whatis"); proc.setArguments(QStringList() << "-M" << (*it_dir) << "-w" << "*"); proc.setProcessChannelMode( QProcess::ForwardedErrorChannel ); proc.start(); proc.waitForFinished(); QTextStream t( proc.readAllStandardOutput(), QIODevice::ReadOnly ); parseWhatIs( i, t, mark ); } } } return i; } //--------------------------------------------------------------------- QStringList MANProtocol::manDirectories() { checkManPaths(); // // Build a list of man directories including translations // QStringList man_dirs; for ( QStringList::ConstIterator it_dir = m_manpath.constBegin(); it_dir != m_manpath.constEnd(); it_dir++ ) { // Translated pages in "/" if the directory // exists QList locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); for (QList::ConstIterator it_loc = locales.constBegin(); it_loc != locales.constEnd(); it_loc++ ) { QString lang = QLocale::languageToString((*it_loc).language()); if ( !lang.isEmpty() && lang!=QString("C") ) { QString dir = (*it_dir) + '/' + lang; struct stat sbuf; if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 && S_ISDIR( sbuf.st_mode ) ) { const QString p = QDir(dir).canonicalPath(); if (!man_dirs.contains(p)) man_dirs += p; } } } // Untranslated pages in "" const QString p = QDir(*it_dir).canonicalPath(); if (!man_dirs.contains(p)) man_dirs += p; } return man_dirs; } QStringList MANProtocol::findPages(const QString &_section, const QString &title, bool full_path) { QString section = _section; QStringList list; // kDebug() << "findPages '" << section << "' '" << title << "'\n"; if ( (!title.isEmpty()) && (title.at(0) == '/') ) { list.append(title); return list; } const QString star( "*" ); // // Find man sections in this directory // QStringList sect_list; if ( section.isEmpty() ) section = star; if ( section != star ) { // // Section given as argument // sect_list += section; while ( (!section.isEmpty()) && (section.at(section.length() - 1).isLetter()) ) { section.truncate(section.length() - 1); sect_list += section; } } else { sect_list += section; } QStringList man_dirs = manDirectories(); // // Find man pages in the sections listed above // for ( int i=0;id_name ); QString sect; if ( file.startsWith( man ) ) sect = file.mid(3); else if (file.startsWith(sman)) sect = file.mid(4); if (sect.toLower()==it_real) it_real = sect; // Only add sect if not already contained, avoid duplicates if (!sect_list.contains(sect) && _section.isEmpty()) { //kDebug() << "another section " << sect; sect_list += sect; } } //qCDebug(KIO_MAN_LOG) <<" after while loop"; ::closedir( dp ); #if 0 qCDebug(KIO_MAN_LOG)<<"================-"; qCDebug(KIO_MAN_LOG)<<"star=="<d_name[0] != '.' ) { QString name = QFile::decodeName( ep->d_name ); // check title if we're looking for a specific page if ( title_given ) { if ( !name.startsWith( title ) ) { continue; } else { // beginning matches, do a more thorough check... QString tmp_name = name; stripExtension( &tmp_name ); if ( tmp_name != title ) continue; } } if ( full_path ) name.prepend( dir ); list += name ; } } ::closedir( dp ); } void MANProtocol::output(const char *insert) { if (insert) { m_outputBuffer.write(insert,strlen(insert)); } if (!insert || m_outputBuffer.pos() >= 2048) { m_outputBuffer.close(); data(m_outputBuffer.buffer()); m_outputBuffer.setData(QByteArray()); m_outputBuffer.open(QIODevice::WriteOnly); } } #ifndef SIMPLE_MAN2HTML // called by man2html extern char *read_man_page(const char *filename) { return MANProtocol::self()->readManPage(filename); } // called by man2html extern void output_real(const char *insert) { MANProtocol::self()->output(insert); } #endif void MANProtocol::get(const QUrl& url ) { qCDebug(KIO_MAN_LOG) << "GET " << url.url(); QString title, section; if (!parseUrl(url.path(), title, section)) { showMainIndex(); return; } // tell the mimetype mimeType("text/html"); // see if an index was requested if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == ".")) { if (section == "index" || section.isEmpty()) showMainIndex(); else showIndex(section); return; } const QStringList foundPages=findPages(section, title); bool pageFound=true; if (foundPages.isEmpty()) { outputError(i18n("No man page matching to %1 found.

" "Check that you have not mistyped the name of the page that you want.
" "Check that you have typed the name using the correct upper and lower case characters.
" "If everything looks correct, then you may need to improve the search path " "for man pages; either using the environment variable MANPATH or using a matching file " "in the /etc directory.", title.toHtmlEscaped())); pageFound=false; } else if (foundPages.count()>1) { pageFound=false; //check for the case that there is foo.1 and foo.1.gz found: // ### TODO make it more generic (other extensions) if ((foundPages.count()==2) && ((QString(foundPages[0]+".gz") == foundPages[1]) || (foundPages[0] == QString(foundPages[1]+".gz")))) pageFound=true; else outputMatchingPages(foundPages); } //yes, we found exactly one man page if (pageFound) { setCssFile(m_manCSSFile); m_outputBuffer.open(QIODevice::WriteOnly); const QByteArray filename=QFile::encodeName(foundPages[0]); char *buf = readManPage(filename); if (!buf) { outputError(i18n("Open of %1 failed.", title)); finished(); return; } // will call output_real scan_man_page(buf); delete [] buf; output(nullptr); // flush m_outputBuffer.close(); data(m_outputBuffer.buffer()); m_outputBuffer.setData(QByteArray()); // tell we are done data(QByteArray()); } finished(); } //--------------------------------------------------------------------- char *MANProtocol::readManPage(const char *_filename) { QByteArray filename = _filename; QByteArray array, dirName; /* Determine type of man page file by checking its path. Determination by * MIME type with KMimeType doesn't work reliably. E.g., Solaris 7: * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG * If the path name contains the string sman, assume that it's SGML and * convert it to roff format (used on Solaris). */ //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name(); if (QString(filename).contains("sman", Qt::CaseInsensitive)) //file_mimetype == "text/html" || ) { QProcess proc; // Determine path to sgml2roff, if not already done. getProgramPath(); proc.setProgram(mySgml2RoffPath); proc.setArguments(QStringList() << filename); proc.setProcessChannelMode( QProcess::ForwardedErrorChannel ); proc.start(); proc.waitForFinished(); array = proc.readAllStandardOutput(); } else { if (QDir::isRelativePath(filename)) { qCDebug(KIO_MAN_LOG) << "relative " << filename; filename = QDir::cleanPath(lastdir + '/' + filename).toUtf8(); qCDebug(KIO_MAN_LOG) << "resolved to " << filename; } lastdir = filename.left(filename.lastIndexOf('/')); // get the last directory name (which might be a language name, to be able to guess the encoding) QDir dir(lastdir); dir.cdUp(); dirName = QFile::encodeName(dir.dirName()); if ( !QFile::exists(QFile::decodeName(filename)) ) // if given file does not exist, find with suffix { qCDebug(KIO_MAN_LOG) << "not existing " << filename; QDir mandir(lastdir); const QString nameFilter = filename.mid(filename.lastIndexOf('/') + 1) + ".*"; mandir.setNameFilters(QStringList(nameFilter)); const QStringList entries = mandir.entryList(); if (entries.isEmpty()) { outputError(i18n("The specified man page referenced another page '%1',
" "but the referenced page '%2' could not be found.", QFile::decodeName(filename), QDir::cleanPath(lastdir + '/' + nameFilter))); return 0; } filename = lastdir + '/' + QFile::encodeName(entries.first()); qCDebug(KIO_MAN_LOG) << "resolved to " << filename; } KFilterDev fd(QFile::encodeName(filename)); if ( !fd.open(QIODevice::ReadOnly)) return nullptr; array = fd.readAll(); qCDebug(KIO_MAN_LOG) << "read " << array.size(); } if (array.isEmpty()) return nullptr; return manPageToUtf8(array, dirName); } //--------------------------------------------------------------------- void MANProtocol::outputError(const QString& errmsg) { QByteArray array; QTextStream os(&array, QIODevice::WriteOnly); os.setCodec( "UTF-8" ); - os << "" << endl; - os << "" << endl; - os << "" << i18n("Man output") << "\n" << endl; + os << "\n"; + os << "\n"; + os << "" << i18n("Man output") << "\n\n"; if ( !m_manCSSFile.isEmpty() ) - os << "" << endl; - os << "" << endl; - os << "" << i18n("

KDE Man Viewer Error

") << errmsg << "" << endl; - os << "" << endl; + os << "\n"; + os << "\n"; + os << "" << i18n("

KDE Man Viewer Error

") << errmsg << "\n"; + os << "\n"; + os.flush(); data(array); } void MANProtocol::outputMatchingPages(const QStringList &matchingPages) { QByteArray array; QTextStream os(&array, QIODevice::WriteOnly); os.setCodec( "UTF-8" ); - os << "" << endl; - os << "\n"<" << i18n("Man output") <<"" << endl; + os << "\n"; + os << "\n\n"; + os << "" << i18n("Man output") <<"\n"; if ( !m_manCSSFile.isEmpty() ) - os << "" << endl; - os << "" <\n"; + os << "\n"; os << "

" << i18n("There is more than one matching man page."); os << "

\n
    \n"; int acckey=1; for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it) { os<<"
  • "<< *it <<"
    \n
    \n"; acckey++; } os << "
\n"; os << "
\n"; os << "

" << i18n("Note: if you read a man page in your language," " be aware it can contain some mistakes or be obsolete." " In case of doubt, you should have a look at the English version.") << "

"; - os << "\n"<\n\n"; + os.flush(); data(array); finished(); } void MANProtocol::stat( const QUrl& url) { qCDebug(KIO_MAN_LOG) << "ENTERING STAT " << url.url(); QString title, section; if (!parseUrl(url.path(), title, section)) { error(KIO::ERR_MALFORMED_URL, url.url()); return; } qCDebug(KIO_MAN_LOG) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section; UDSEntry entry; entry.fastInsert(KIO::UDSEntry::UDS_NAME, title); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/html")); #if 0 // not useful, is it? QString newUrl = "man:"+title; if (!section.isEmpty()) newUrl += QString("(%1)").arg(section); entry.insert(KIO::UDSEntry::UDS_URL, newUrl); #endif statEntry(entry); finished(); } extern "C" { int Q_DECL_EXPORT kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName(QLatin1String("kio_man")); qCDebug(KIO_MAN_LOG) << "STARTING"; if (argc != 4) { fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n"); exit(-1); } MANProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(KIO_MAN_LOG) << "Done"; return 0; } } void MANProtocol::mimetype(const QUrl & /*url*/) { mimeType("text/html"); finished(); } static QString sectionName(const QString& section) { if (section == "0") return i18n("Header files"); else if (section == "0p") return i18n("Header files (POSIX)"); else if (section == "1") return i18n("User Commands"); else if (section == "1p") return i18n("User Commands (POSIX)"); else if (section == "2") return i18n("System Calls"); else if (section == "3") return i18n("Subroutines"); else if (section == "3p") return i18n("Perl Modules"); else if (section == "3n") return i18n("Network Functions"); else if (section == "4") return i18n("Devices"); else if (section == "5") return i18n("File Formats"); else if (section == "6") return i18n("Games"); else if (section == "7") return i18n("Miscellaneous"); else if (section == "8") return i18n("System Administration"); else if (section == "9") return i18n("Kernel"); else if (section == "l") return i18n("Local Documentation"); else if (section == "n") return i18n("New"); return QString(); } QStringList MANProtocol::buildSectionList(const QStringList& dirs) const { QStringList l; for (QStringList::ConstIterator it = section_names.begin(); it != section_names.end(); ++it) { for (QStringList::ConstIterator dir = dirs.begin(); dir != dirs.end(); ++dir) { QDir d((*dir)+"/man"+(*it)); if (d.exists()) { l << *it; break; } } } return l; } void MANProtocol::showMainIndex() { QByteArray array; QTextStream os(&array, QIODevice::WriteOnly); os.setCodec( "UTF-8" ); // print header - os << "" << endl; - os << "" << endl; - os << "" << i18n("UNIX Manual Index") << "" << endl; + os << "\n"; + os << "\n"; + os << "" << i18n("UNIX Manual Index") << "\n"; if (!m_manCSSFile.isEmpty()) - os << "" << endl; - os << "" << endl; - os << "

" << i18n("UNIX Manual Index") << "

" << endl; + os << "\n"; + os << "\n"; + os << "

" << i18n("UNIX Manual Index") << "

\n"; // ### TODO: why still the environment variable const QString sectList = getenv("MANSECT"); QStringList sections; if (sectList.isEmpty()) sections = buildSectionList(manDirectories()); else sections = sectList.split(':'); - os << "" << endl; + os << "
\n"; QSet accessKeys; char alternateAccessKey = 'a'; QStringList::ConstIterator it; for (it = sections.constBegin(); it != sections.constEnd(); ++it) { // create a unique access key QChar accessKey = (*it).at((*it).length() - 1); // rightmost char while ( accessKeys.contains(accessKey) ) accessKey = alternateAccessKey++; accessKeys.insert(accessKey); os << "" << endl; + << "\n"; } - os << "
" << i18n("Section %1", *it) - << "  " << sectionName(*it) << "
  " << sectionName(*it) << "
" << endl; + os << "\n"; // print footer - os << "" << endl; + os << "\n"; + os.flush(); data(array); finished(); } void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath) { QMap manpath_map; QMap mandb_map; // Add paths from /etc/man.conf // // Explicit manpaths may be given by lines starting with "MANPATH" or // "MANDATORY_MANPATH" (depending on system ?). // Mappings from $PATH to manpath are given by lines starting with // "MANPATH_MAP" QRegExp manpath_regex( "^MANPATH\\s" ); QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" ); QRegExp manpath_map_regex( "^MANPATH_MAP\\s" ); QRegExp mandb_map_regex( "^MANDB_MAP\\s" ); //QRegExp section_regex( "^SECTION\\s" ); QRegExp space_regex( "\\s+" ); // for parsing manpath map QFile mc("/etc/man.conf"); // Caldera if (!mc.exists()) mc.setFileName("/etc/manpath.config"); // SuSE, Debian if (!mc.exists()) mc.setFileName("/etc/man.config"); // Mandrake if (mc.open(QIODevice::ReadOnly)) { QTextStream is(&mc); is.setCodec( QTextCodec::codecForLocale () ); while (!is.atEnd()) { const QString line = is.readLine(); if ( manpath_regex.indexIn(line) == 0 ) { const QString path = line.mid(8).trimmed(); constr_path += path; } else if ( mandatory_regex.indexIn(line) == 0 ) { const QString path = line.mid(18).trimmed(); constr_path += path; } else if ( manpath_map_regex.indexIn(line) == 0 ) { // The entry is "MANPATH_MAP " const QStringList mapping = line.split( space_regex); if ( mapping.count() == 3 ) { const QString dir = QDir::cleanPath( mapping[1] ); const QString mandir = QDir::cleanPath( mapping[2] ); manpath_map[ dir ] = mandir; } } else if ( mandb_map_regex.indexIn(line) == 0 ) { // The entry is "MANDB_MAP " const QStringList mapping = line.split( space_regex); if ( mapping.count() == 3 ) { const QString mandir = QDir::cleanPath( mapping[1] ); const QString catmandir = QDir::cleanPath( mapping[2] ); mandb_map[ mandir ] = catmandir; } } /* sections are not used else if ( section_regex.find(line, 0) == 0 ) { if ( !conf_section.isEmpty() ) conf_section += ':'; conf_section += line.mid(8).trimmed(); } */ } mc.close(); } // Default paths static const char * const manpaths[] = { "/usr/X11/man", "/usr/X11R6/man", "/usr/man", "/usr/local/man", "/usr/exp/man", "/usr/openwin/man", "/usr/dt/man", "/opt/freetool/man", "/opt/local/man", "/usr/tex/man", "/usr/www/man", "/usr/lang/man", "/usr/gnu/man", "/usr/share/man", "/usr/motif/man", "/usr/titools/man", "/usr/sunpc/man", "/usr/ncd/man", "/usr/newsprint/man", nullptr }; int i = 0; while (manpaths[i]) { if ( constr_path.indexOf( QString( manpaths[i] ) ) == -1 ) constr_path += QString( manpaths[i] ); i++; } // Directories in $PATH // - if a manpath mapping exists, use that mapping // - if a directory "/man" or "/../man" exists, add it // to the man path (the actual existence check is done further down) if ( ::getenv("PATH") ) { const QStringList path = QString::fromLocal8Bit( ::getenv("PATH") ).split( ':', QString::SkipEmptyParts ); for ( QStringList::const_iterator it = path.constBegin(); it != path.constEnd(); ++it ) { const QString dir = QDir::cleanPath( *it ); QString mandir = manpath_map[ dir ]; if ( !mandir.isEmpty() ) { // a path mapping exists if ( constr_path.indexOf( mandir ) == -1 ) constr_path += mandir; } else { // no manpath mapping, use "/man" and "/../man" mandir = dir + QString( "/man" ); if ( constr_path.indexOf( mandir ) == -1 ) constr_path += mandir; int pos = dir.lastIndexOf( '/' ); if ( pos > 0 ) { mandir = dir.left( pos ) + QString("/man"); if ( constr_path.indexOf( mandir ) == -1 ) constr_path += mandir; } } QString catmandir = mandb_map[ mandir ]; if ( !mandir.isEmpty() ) { if ( constr_catmanpath.indexOf( catmandir ) == -1 ) constr_catmanpath += catmandir; } else { // What is the default mapping? catmandir = mandir; catmandir.replace("/usr/share/","/var/cache/"); if ( constr_catmanpath.indexOf( catmandir ) == -1 ) constr_catmanpath += catmandir; } } } } void MANProtocol::checkManPaths() { static bool inited = false; if (inited) return; inited = true; const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") ); //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") ); // Decide if $MANPATH is enough on its own or if it should be merged // with the constructed path. // A $MANPATH starting or ending with ":", or containing "::", // should be merged with the constructed path. bool construct_path = false; if ( manpath_env.isEmpty() || manpath_env[0] == ':' || manpath_env[manpath_env.length()-1] == ':' || manpath_env.contains( "::" ) ) { construct_path = true; // need to read config file } // Constructed man path -- consists of paths from // /etc/man.conf // default dirs // $PATH QStringList constr_path; QStringList constr_catmanpath; // catmanpath QString conf_section; if ( construct_path ) { constructPath(constr_path, constr_catmanpath); } m_mandbpath=constr_catmanpath; // Merge $MANPATH with the constructed path to form the // actual manpath. // // The merging syntax with ":" and "::" in $MANPATH will be // satisfied if any empty string in path_list_env (there // should be 1 or 0) is replaced by the constructed path. const QStringList path_list_env = manpath_env.split( ':', QString::KeepEmptyParts); for ( QStringList::const_iterator it = path_list_env.constBegin(); it != path_list_env.constEnd(); ++it ) { struct stat sbuf; QString dir = (*it); if ( !dir.isEmpty() ) { // Add dir to the man path if it exists if ( m_manpath.indexOf( dir ) == -1 ) { if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 && S_ISDIR( sbuf.st_mode ) ) { m_manpath += dir; } } } else { // Insert constructed path ($MANPATH was empty, or // there was a ":" at an end or "::") for ( QStringList::const_iterator it2 = constr_path.constBegin(); it2 != constr_path.constEnd(); it2++ ) { dir = (*it2); if ( !dir.isEmpty() ) { if ( m_manpath.indexOf( dir ) == -1 ) { if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 && S_ISDIR( sbuf.st_mode ) ) { m_manpath += dir; } } } } } } /* sections are not used // Sections QStringList m_mansect = mansect_env.split( ':', QString::KeepEmptyParts); const char* const default_sect[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L }; for ( int i = 0; default_sect[i] != 0L; i++ ) if ( m_mansect.indexOf( QString( default_sect[i] ) ) == -1 ) m_mansect += QString( default_sect[i] ); */ } // Setup my own structure, with char pointers. // from now on only pointers are copied, no strings // // containing the whole path string, // the beginning of the man page name // and the length of the name struct man_index_t { char *manpath; // the full path including man file const char *manpage_begin; // pointer to the begin of the man file name in the path int manpage_len; // len of the man file name }; typedef man_index_t *man_index_ptr; int compare_man_index(const void *s1, const void *s2) { struct man_index_t *m1 = *(struct man_index_t **)s1; struct man_index_t *m2 = *(struct man_index_t **)s2; int i; // Compare the names of the pages // with the shorter length. // Man page names are not '\0' terminated, so // this is a bit tricky if ( m1->manpage_len > m2->manpage_len) { i = qstrnicmp(m1->manpage_begin, m2->manpage_begin, m2->manpage_len); if (!i) return 1; return i; } if ( m1->manpage_len < m2->manpage_len) { i = qstrnicmp(m1->manpage_begin, m2->manpage_begin, m1->manpage_len); if (!i) return -1; return i; } return qstrnicmp(m1->manpage_begin, m2->manpage_begin, m1->manpage_len); } void MANProtocol::showIndex(const QString& section) { QByteArray array_h; QTextStream os_h(&array_h, QIODevice::WriteOnly); os_h.setCodec( "UTF-8" ); // print header - os_h << "" << endl; - os_h << "" << endl; - os_h << "" << i18n("UNIX Manual Index") << "" << endl; + os_h << "\n"; + os_h << "\n"; + os_h << "" << i18n("UNIX Manual Index") << "\n"; if ( !m_manCSSFile.isEmpty() ) - os_h << "" << endl; - os_h << "" << endl << "" << endl; + os_h << "\n"; + os_h << "\n" << "\n"; QByteArray array_d; QTextStream os(&array_d, QIODevice::WriteOnly); os.setCodec( "UTF-8" ); - os << "
" << endl; - os << "

" << i18n( "Index for Section %1: %2", section, sectionName(section)) << "

" << endl; + os << "
\n"; + os << "

" << i18n( "Index for Section %1: %2", section, sectionName(section)) << "

\n"; // compose list of search paths ------------------------------------------------------------- checkManPaths(); infoMessage(i18n("Generating Index")); // search for the man pages QStringList pages = findPages( section, QString() ); if ( pages.count() == 0 ) // not a single page found { // print footer - os << "
" << endl; + os << "
\n"; + os.flush(); + os_h.flush(); infoMessage(QString()); data(array_h + array_d); finished(); return; } QMap indexmap = buildIndexMap(section); // print out the list - os << "" << endl; + os << "
\n"; int listlen = pages.count(); man_index_ptr *indexlist = new man_index_ptr[listlen]; listlen = 0; QStringList::const_iterator page; for (page = pages.constBegin(); page != pages.constEnd(); ++page) { // I look for the beginning of the man page name // i.e. "bla/pagename.3.gz" by looking for the last "/" // Then look for the end of the name by searching backwards // for the last ".", not counting zip extensions. // If the len of the name is >0, // store it in the list structure, to be sorted later char *manpage_end; struct man_index_t *manindex = new man_index_t; manindex->manpath = strdup((*page).toUtf8()); manindex->manpage_begin = strrchr(manindex->manpath, '/'); if (manindex->manpage_begin) { manindex->manpage_begin++; assert(manindex->manpage_begin >= manindex->manpath); } else { manindex->manpage_begin = manindex->manpath; assert(manindex->manpage_begin >= manindex->manpath); } // Skip extension ".section[.gz]" char *begin = (char*)(manindex->manpage_begin); int len = strlen( begin ); char *end = begin+(len-1); if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 ) end -= 3; else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 ) end -= 2; else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 ) end -= 2; else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 ) end -= 4; else if ( len >= 5 && strcmp( end-4, ".lzma" ) == 0 ) end -= 5; else if ( len >= 3 && strcmp( end-2, ".xz" ) == 0 ) end -= 3; while ( end >= begin && *end != '.' ) end--; if ( end < begin ) manpage_end = nullptr; else manpage_end = end; if (nullptr == manpage_end) { // no '.' ending ??? // set the pointer past the end of the filename manindex->manpage_len = (*page).length(); manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath); assert(manindex->manpage_len >= 0); } else { manindex->manpage_len = (manpage_end - manindex->manpage_begin); assert(manindex->manpage_len >= 0); } if (0 < manindex->manpage_len) { indexlist[listlen] = manindex; listlen++; } else delete manindex; } // // Now do the sorting on the page names // and the printout afterwards // While printing avoid duplicate man page names // struct man_index_t dummy_index = {nullptr,nullptr,0}; struct man_index_t *last_index = &dummy_index; // sort and print qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index); QChar firstchar, tmp; QString indexLine="
\n"; if (indexlist[0]->manpage_len>0) { firstchar=QChar((indexlist[0]->manpage_begin)[0]).toLower(); const QString appendixstr = QString( " [%3]\n" ).arg(firstchar).arg(firstchar).arg(firstchar); indexLine.append(appendixstr); } os << "
" << endl; + << firstchar << "\">" << firstchar <<"\n\n"; for (int i=0; imanpage_begin" has not, // so do compare at most "manindex->manpage_len" of the strings. if (last_index->manpage_len == manindex->manpage_len && !qstrncmp(last_index->manpage_begin, manindex->manpage_begin, manindex->manpage_len) ) { continue; } tmp=QChar((manindex->manpage_begin)[0]).toLower(); if (firstchar != tmp) { firstchar = tmp; os << "" << endl; + << firstchar << "\">" << firstchar << "\n\n"; const QString appendixstr = QString( " [%3]\n" ).arg(firstchar).arg(firstchar).arg(firstchar); indexLine.append(appendixstr); } os << "" << endl; + << "\n"; last_index = manindex; } indexLine.append(""); for (int i=0; imanpath); // allocated by strdup delete indexlist[i]; } delete [] indexlist; - os << "
\n " << firstchar <<"\n
\n " << firstchar << "\n
manpath << "\">\n"; ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0'; os << manindex->manpage_begin << "  " << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" ) - << "
" << endl; + os << "\n"; - os << indexLine << endl; + os << indexLine << '\n'; // print footer - os << "" << endl; + os << "\n"; // set the links "toolbar" also at the top - os_h << indexLine << endl; + os_h << indexLine << '\n'; + os.flush(); + os_h.flush(); infoMessage(QString()); data(array_h + array_d); finished(); } void MANProtocol::listDir(const QUrl &url) { qCDebug(KIO_MAN_LOG) << url; QString title; QString section; if ( !parseUrl(url.path(), title, section) ) { error( KIO::ERR_MALFORMED_URL, url.url() ); return; } // stat() and listDir() declared that everything is an html file. // However we can list man: and man:(1) as a directory (e.g. in dolphin). // But we cannot list man:ls as a directory, this makes no sense (#154173) if (!title.isEmpty() && title != "/") { error(KIO::ERR_IS_FILE, url.url()); return; } UDSEntryList uds_entry_list; if (section.isEmpty()) { for (QStringList::ConstIterator it = section_names.constBegin(); it != section_names.constEnd(); ++it) { UDSEntry uds_entry; QString name = "man:/(" + *it + ')'; uds_entry.fastInsert( KIO::UDSEntry::UDS_NAME, sectionName( *it ) ); uds_entry.fastInsert( KIO::UDSEntry::UDS_URL, name ); uds_entry.fastInsert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); uds_entry_list.append( uds_entry ); } } QStringList list = findPages( section, QString(), false ); QStringList::Iterator it = list.begin(); QStringList::Iterator end = list.end(); for ( ; it != end; ++it ) { stripExtension( &(*it) ); UDSEntry uds_entry; uds_entry.fastInsert(KIO::UDSEntry::UDS_NAME, *it); uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); uds_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/html")); uds_entry_list.append( uds_entry ); } listEntries( uds_entry_list ); finished(); } void MANProtocol::getProgramPath() { if (!mySgml2RoffPath.isEmpty()) return; mySgml2RoffPath = QStandardPaths::findExecutable("sgml2roff"); if (!mySgml2RoffPath.isEmpty()) return; /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */ mySgml2RoffPath = QStandardPaths::findExecutable("sgml2roff", QStringList(QLatin1String(SGML2ROFF_DIRS))); if (!mySgml2RoffPath.isEmpty()) return; /* Cannot find sgml2roff program: */ 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.")); finished(); exit(); } diff --git a/smb/kio_smb.cpp b/smb/kio_smb.cpp index 94dc442d..0dd8f4dd 100644 --- a/smb/kio_smb.cpp +++ b/smb/kio_smb.cpp @@ -1,113 +1,112 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE // // File: Top level implementation file for kio_smb.cpp // // Abstract: member function implementations for SMBSlave // // Author(s): Matthew Peterson // //--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, please obtain // a copy from https://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #include "kio_smb.h" #include "kio_smb_internal.h" #include #include Q_LOGGING_CATEGORY(KIO_SMB, "kio_smb") bool needsEEXISTWorkaround() { /* There is an issue with some libsmbclient versions that return EEXIST * return code from smbc_opendir() instead of EPERM when the user * tries to access a resource that requires login authetification. * We are working around the issue by treating EEXIST as a special case * of "invalid/unavailable credentials" if we detect that we are using * the affected versions of libsmbclient * * Upstream bug report: https://bugzilla.samba.org/show_bug.cgi?id=13050 */ static const QVersionNumber firstBrokenVer{4, 7, 0}; static const QVersionNumber lastBrokenVer{4, 7, 6}; const QVersionNumber currentVer = QVersionNumber::fromString(smbc_version()); qCDebug(KIO_SMB) << "Using libsmbclient library version" << currentVer; if (currentVer >= firstBrokenVer && currentVer <= lastBrokenVer) { qCDebug(KIO_SMB) << "Detected broken libsmbclient version" << currentVer; return true; } return false; } //=========================================================================== SMBSlave::SMBSlave(const QByteArray& pool, const QByteArray& app) : SlaveBase( "smb", pool, app ), m_openFd(-1), m_enableEEXISTWorkaround(needsEEXISTWorkaround()) { m_initialized_smbc = false; //read in the default workgroup info... reparseConfiguration(); //initialize the library... auth_initialize_smbc(); } //=========================================================================== SMBSlave::~SMBSlave() { } void SMBSlave::virtual_hook(int id, void *data) { switch(id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; default: { SlaveBase::virtual_hook(id, data); } break; } } //=========================================================================== int Q_DECL_EXPORT kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); if( argc != 4 ) { - qCDebug(KIO_SMB) << "Usage: kio_smb protocol domain-socket1 domain-socket2" - << endl; + qCDebug(KIO_SMB) << "Usage: kio_smb protocol domain-socket1 domain-socket2"; return -1; } SMBSlave slave( argv[2], argv[3] ); slave.dispatchLoop(); return 0; } diff --git a/smb/kio_smb_auth.cpp b/smb/kio_smb_auth.cpp index 9ec3aa4e..3db2634e 100644 --- a/smb/kio_smb_auth.cpp +++ b/smb/kio_smb_auth.cpp @@ -1,228 +1,228 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE2 // // File: kio_smb_auth.cpp // // Abstract: member function implementations for SMBSlave that deal with // SMB directory access // // Author(s): Matthew Peterson // //--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, please obtain // a copy from https://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #include "kio_smb.h" #include "kio_smb_internal.h" #include #include #include #include // call for libsmbclient //========================================================================== void auth_smbc_get_data(SMBCCTX * context, const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen) //========================================================================== { if (context != nullptr) { #ifdef DEPRECATED_SMBC_INTERFACE SMBSlave *theSlave = (SMBSlave*) smbc_getOptionUserData(context); #else SMBSlave *theSlave = (SMBSlave*)smbc_option_get(context, "user_data"); #endif theSlave->auth_smbc_get_data(server, share, workgroup,wgmaxlen, username, unmaxlen, password, pwmaxlen); } } //-------------------------------------------------------------------------- void SMBSlave::auth_smbc_get_data(const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen) //-------------------------------------------------------------------------- { //check this to see if we "really" need to authenticate... SMBUrlType t = m_current_url.getType(); if( t == SMBURLTYPE_ENTIRE_NETWORK ) { qCDebug(KIO_SMB) << "we don't really need to authenticate for this top level url, returning"; return; } qCDebug(KIO_SMB) << "auth_smbc_get_dat: set user=" << username << ", workgroup=" << workgroup - << " server=" << server << ", share=" << share << endl; + << " server=" << server << ", share=" << share; QString s_server = QString::fromUtf8(server); QString s_share = QString::fromUtf8(share); workgroup[wgmaxlen - 1] = 0; QString s_workgroup = QString::fromUtf8(workgroup); username[unmaxlen - 1] = 0; QString s_username = QString::fromUtf8(username); password[pwmaxlen - 1] = 0; QString s_password = QString::fromUtf8(password); KIO::AuthInfo info; info.url = QUrl("smb:///"); info.url.setHost(s_server); info.url.setPath('/' + s_share); info.username = s_username; info.password = s_password; info.verifyPath = true; qCDebug(KIO_SMB) << "libsmb-auth-callback URL:" << info.url; if ( !checkCachedAuthentication( info ) ) { if ( m_default_user.isEmpty() ) { // ok, we do not know the password. Let's try anonymous before we try for real info.username = "anonymous"; info.password.clear(); } else { // user defined a default username/password in kcontrol; try this info.username = m_default_user; info.password = m_default_password; } } else qCDebug(KIO_SMB) << "got password through cache"; strncpy(username, info.username.toUtf8(), unmaxlen - 1); strncpy(password, info.password.toUtf8(), pwmaxlen - 1); } int SMBSlave::checkPassword(SMBUrl &url) { qCDebug(KIO_SMB) << "checkPassword for " << url; KIO::AuthInfo info; info.url = QUrl("smb:///"); info.url.setHost(url.host()); QString share = url.path(); int index = share.indexOf('/', 1); if (index > 1) share = share.left(index); if (share.at(0) == '/') share = share.mid(1); info.url.setPath('/' + share); info.verifyPath = true; info.keepPassword = true; if ( share.isEmpty() ) info.prompt = i18n( "Please enter authentication information for %1" , url.host() ); else info.prompt = i18n( "Please enter authentication information for:\n" "Server = %1\n" "Share = %2" , url.host() , share ); info.username = url.userName(); qCDebug(KIO_SMB) << "call openPasswordDialog for " << info.url; const int passwordDialogErrorCode = openPasswordDialogV2(info); if (passwordDialogErrorCode == KJob::NoError) { qCDebug(KIO_SMB) << "openPasswordDialog returned " << info.username; url.setUser(info.username); if (info.keepPassword) { qCDebug(KIO_SMB) << "Caching info.username = " << info.username << ", info.url = " << info.url.toDisplayString(); cacheAuthentication(info); } return KJob::NoError; } qCDebug(KIO_SMB) << "no value from openPasswordDialog; error:" << passwordDialogErrorCode; return passwordDialogErrorCode; } //-------------------------------------------------------------------------- // Initializes the smbclient library // // Returns: 0 on success -1 with errno set on error bool SMBSlave::auth_initialize_smbc() { SMBCCTX *smb_context = nullptr; qCDebug(KIO_SMB) << "auth_initialize_smbc "; if(m_initialized_smbc == false) { qCDebug(KIO_SMB) << "smbc_init call"; KConfig cfg( "kioslaverc", KConfig::SimpleConfig); int debug_level = cfg.group( "SMB" ).readEntry( "DebugLevel", 0 ); smb_context = smbc_new_context(); if (smb_context == nullptr) { SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to create context")); return false; } #ifdef DEPRECATED_SMBC_INTERFACE // defined by libsmbclient.h of Samba 3.2 /* New libsmbclient interface of Samba 3.2 */ smbc_setDebug(smb_context, debug_level); smbc_setFunctionAuthDataWithContext(smb_context, ::auth_smbc_get_data); smbc_setOptionUserData(smb_context, this); /* Enable Kerberos support */ smbc_setOptionUseKerberos(smb_context, 1); smbc_setOptionFallbackAfterKerberos(smb_context, 1); #else smb_context->debug = debug_level; smb_context->callbacks.auth_fn = NULL; smbc_option_set(smb_context, "auth_function", (void*)::auth_smbc_get_data); smbc_option_set(smb_context, "user_data", this); #if defined(SMB_CTX_FLAG_USE_KERBEROS) && defined(SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS) smb_context->flags |= SMB_CTX_FLAG_USE_KERBEROS | SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS; #endif #endif /* DEPRECATED_SMBC_INTERFACE */ if (!smbc_init_context(smb_context)) { smbc_free_context(smb_context, 0); smb_context = nullptr; SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to initialize context")); return false; } smbc_set_context(smb_context); m_initialized_smbc = true; } return true; } diff --git a/smb/kio_smb_mount.cpp b/smb/kio_smb_mount.cpp index 8e4ff90f..a0aaf5d7 100644 --- a/smb/kio_smb_mount.cpp +++ b/smb/kio_smb_mount.cpp @@ -1,193 +1,193 @@ /* This file is part of the KDE project Copyright (C) 2000 Alexander Neundorf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kio_smb.h" #include #include #include #include #include #include #include void SMBSlave::special( const QByteArray & data) { qCDebug(KIO_SMB)<<"Smb::special()"; int tmp; QDataStream stream(data); stream >> tmp; //mounting and umounting are both blocking, "guarded" by a SIGALARM in the future switch (tmp) { case 1: case 3: { QString remotePath, mountPoint, user; stream >> remotePath >> mountPoint; QStringList sl=remotePath.split('/'); QString share,host; if (sl.count()>=2) { host=sl.at(0).mid(2); share=sl.at(1); qCDebug(KIO_SMB)<<"special() host -"<< host <<"- share -" << share <<"-"; } remotePath.replace('\\', '/'); // smbmounterplugin sends \\host/share qCDebug(KIO_SMB) << "mounting: " << remotePath.toLocal8Bit() << " to " << mountPoint.toLocal8Bit(); if (tmp==3) { if (!QDir().mkpath(mountPoint)) { error(KIO::ERR_CANNOT_MKDIR, mountPoint); return; } } SMBUrl smburl(QUrl("smb:///")); smburl.setHost(host); smburl.setPath('/' + share); const int passwordError = checkPassword(smburl); if (passwordError != KJob::NoError && passwordError != KIO::ERR_USER_CANCELED) { error(passwordError, smburl.toString()); return; } // using smbmount instead of "mount -t smbfs", because mount does not allow a non-root // user to do a mount, but a suid smbmnt does allow this KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); proc << "smbmount"; QString options; if ( smburl.userName().isEmpty() ) { user = "guest"; options = "guest"; } else { options = "username=" + smburl.userName(); user = smburl.userName(); if ( ! smburl.password().isEmpty() ) options += ",password=" + smburl.password(); } // TODO: check why the control center uses encodings with a blank char, e.g. "cp 1250" //if ( ! m_default_encoding.isEmpty() ) //options += ",codepage=" + KShell::quoteArg(m_default_encoding); proc << remotePath; proc << mountPoint; proc << "-o" << options; proc.start(); if (!proc.waitForFinished()) { error(KIO::ERR_CANNOT_LAUNCH_PROCESS, "smbmount"+i18n("\nMake sure that the samba package is installed properly on your system.")); return; } QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput()); QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError()); qCDebug(KIO_SMB) << "mount exit " << proc.exitCode() - << "stdout:" << mybuf << endl << "stderr:" << mystderr << endl; + << "stdout:" << mybuf << "\nstderr:" << mystderr; if (proc.exitCode() != 0) { error( KIO::ERR_CANNOT_MOUNT, i18n("Mounting of share \"%1\" from host \"%2\" by user \"%3\" failed.\n%4", share, host, user, mybuf + '\n' + mystderr)); return; } finished(); } break; case 2: case 4: { QString mountPoint; stream >> mountPoint; KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); proc << "smbumount"; proc << mountPoint; proc.start(); if ( !proc.waitForFinished() ) { error(KIO::ERR_CANNOT_LAUNCH_PROCESS, "smbumount"+i18n("\nMake sure that the samba package is installed properly on your system.")); return; } QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput()); QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError()); qCDebug(KIO_SMB) << "smbumount exit " << proc.exitCode() - << "stdout:" << mybuf << endl << "stderr:" << mystderr << endl; + << "stdout:" << mybuf << "\nstderr:" << mystderr; if (proc.exitCode() != 0) { error(KIO::ERR_CANNOT_UNMOUNT, i18n("Unmounting of mountpoint \"%1\" failed.\n%2", mountPoint, mybuf + '\n' + mystderr)); return; } if ( tmp == 4 ) { bool ok; QDir dir(mountPoint); dir.cdUp(); ok = dir.rmdir(mountPoint); if ( ok ) { QString p=dir.path(); dir.cdUp(); ok = dir.rmdir(p); } if ( !ok ) { error(KIO::ERR_CANNOT_RMDIR, mountPoint); return; } } finished(); } break; default: break; } finished(); } diff --git a/thumbnail/thumbnail.cpp b/thumbnail/thumbnail.cpp index 374fdb35..fe450cf6 100644 --- a/thumbnail/thumbnail.cpp +++ b/thumbnail/thumbnail.cpp @@ -1,780 +1,780 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Malte Starostik 2000 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "thumbnail.h" #include #ifdef __FreeBSD__ #include #endif #include #ifndef Q_OS_WIN #include #include #include // nice() #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Use correctly KComponentData instead of KApplication (but then no QPixmap) #undef USE_KINSTANCE // Fix thumbnail: protocol #define THUMBNAIL_HACK (1) #ifdef THUMBNAIL_HACK # include #endif #include "imagefilter.h" // Recognized metadata entries: // mimeType - the mime type of the file, used for the overlay icon if any // width - maximum width for the thumbnail // height - maximum height for the thumbnail // iconSize - the size of the overlay icon to use if any // iconAlpha - the transparency value used for icon overlays // plugin - the name of the plugin library to be used for thumbnail creation. // Provided by the application to save an addition KTrader // query here. // enabledPlugins - a list of enabled thumbnailer plugins. PreviewJob does not call // this thumbnail slave when a given plugin isn't enabled. However, // for directory thumbnails it doesn't know that the thumbnailer // internally also loads the plugins. // shmid - the shared memory segment id to write the image's data to. // The segment is assumed to provide enough space for a 32-bit // image sized width x height pixels. // If this is given, the data returned by the slave will be: // int width // int height // int depth // Otherwise, the data returned is the image in PNG format. using namespace KIO; //using namespace KCodecs; extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv ) { #ifdef HAVE_NICE nice( 5 ); #endif #ifdef USE_KINSTANCE KComponentData componentData("kio_thumbnail"); #else QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // creating KApplication in a slave in not a very good idea, // as dispatchLoop() doesn't allow it to process its messages, // so it for example wouldn't reply to ksmserver - on the other // hand, this slave uses QPixmaps for some reason, and they // need QApplication // and HTML previews need even KApplication :( putenv(strdup("SESSION_MANAGER=")); // some thumbnail plugins reuse QWidget-tainted code for the rendering, // so use QApplication here, not just QGuiApplication QApplication app(argc, argv); #endif if (argc != 4) { - qCritical() << "Usage: kio_thumbnail protocol domain-socket1 domain-socket2" << endl; + qCritical() << "Usage: kio_thumbnail protocol domain-socket1 domain-socket2"; exit(-1); } ThumbnailProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } ThumbnailProtocol::ThumbnailProtocol(const QByteArray &pool, const QByteArray &app) : SlaveBase("thumbnail", pool, app), m_iconSize(0), m_maxFileSize(0) { } ThumbnailProtocol::~ThumbnailProtocol() { qDeleteAll( m_creators ); m_creators.clear(); } void ThumbnailProtocol::get(const QUrl &url) { m_mimeType = metaData("mimeType"); m_enabledPlugins = metaData("enabledPlugins").split(QLatin1Char(','), QString::SkipEmptyParts); if (m_enabledPlugins.isEmpty()) { const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); } //qDebug() << "Wanting MIME Type:" << m_mimeType; #ifdef THUMBNAIL_HACK // ### HACK bool direct=false; if (m_mimeType.isEmpty()) { QFileInfo info(url.path()); //qDebug() << "PATH: " << url.path() << "isDir:" << info.isDir(); if (!info.exists()) { // The file does not exist error(KIO::ERR_DOES_NOT_EXIST,url.path()); return; } else if (!info.isReadable()) { // The file is not readable! error(KIO::ERR_CANNOT_READ,url.path()); return; } if (info.isDir()) { m_mimeType = "inode/directory"; } else { const QMimeDatabase db; m_mimeType = db.mimeTypeForUrl(QUrl(info.filePath())).name(); } //qDebug() << "Guessing MIME Type:" << m_mimeType; direct=true; // thumbnail: URL was probably typed in Konqueror } #endif if (m_mimeType.isEmpty()) { error(KIO::ERR_INTERNAL, i18n("No MIME Type specified.")); return; } m_width = metaData("width").toInt(); m_height = metaData("height").toInt(); int iconSize = metaData("iconSize").toInt(); if (m_width < 0 || m_height < 0) { error(KIO::ERR_INTERNAL, i18n("No or invalid size specified.")); return; } #ifdef THUMBNAIL_HACK else if (!m_width || !m_height) { //qDebug() << "Guessing height, width, icon size!"; m_width = 128; m_height = 128; iconSize = 128; } #endif if (!iconSize) { iconSize = KIconLoader::global()->currentSize(KIconLoader::Desktop); } if (iconSize != m_iconSize) { m_iconDict.clear(); } m_iconSize = iconSize; m_iconAlpha = metaData("iconAlpha").toInt(); QImage img; KConfigGroup group( KSharedConfig::openConfig(), "PreviewSettings" ); bool kfmiThumb = false; // TODO Figure out if we can use KFileMetadata as a last resource ThumbCreator::Flags flags = ThumbCreator::None; if (!kfmiThumb) { QString plugin = metaData("plugin"); if ((plugin.isEmpty() || plugin == "directorythumbnail") && m_mimeType == "inode/directory") { img = thumbForDirectory(url); if(img.isNull()) { error(KIO::ERR_INTERNAL, i18n("Cannot create thumbnail for directory")); return; } } else { #ifdef THUMBNAIL_HACK if (plugin.isEmpty()) { plugin = pluginForMimeType(m_mimeType); } //qDebug() << "Guess plugin: " << plugin; #endif if (plugin.isEmpty()) { error(KIO::ERR_INTERNAL, i18n("No plugin specified.")); return; } ThumbCreator* creator = getThumbCreator(plugin); if(!creator) { error(KIO::ERR_INTERNAL, i18n("Cannot load ThumbCreator %1", plugin)); return; } ThumbSequenceCreator* sequenceCreator = dynamic_cast(creator); if(sequenceCreator) sequenceCreator->setSequenceIndex(sequenceIndex()); if (!creator->create(url.path(), m_width, m_height, img)) { error(KIO::ERR_INTERNAL, i18n("Cannot create thumbnail for %1", url.path())); return; } flags = creator->flags(); } } scaleDownImage(img, m_width, m_height); if (flags & ThumbCreator::DrawFrame) { int x2 = img.width() - 1; int y2 = img.height() - 1; // paint a black rectangle around the "page" QPainter p; p.begin( &img ); p.setPen( QColor( 48, 48, 48 )); p.drawLine( x2, 0, x2, y2 ); p.drawLine( 0, y2, x2, y2 ); p.setPen( QColor( 215, 215, 215 )); p.drawLine( 0, 0, x2, 0 ); p.drawLine( 0, 0, 0, y2 ); p.end(); } if ((flags & ThumbCreator::BlendIcon) && KIconLoader::global()->alphaBlending(KIconLoader::Desktop)) { // blending the mimetype icon in QImage icon = getIcon(); int x = img.width() - icon.width() - 4; x = qMax( x, 0 ); int y = img.height() - icon.height() - 6; y = qMax( y, 0 ); QPainter p(&img); p.setOpacity(m_iconAlpha/255.0); p.drawImage(x, y, icon); } if (img.isNull()) { error(KIO::ERR_INTERNAL, i18n("Failed to create a thumbnail.")); return; } const QString shmid = metaData("shmid"); if (shmid.isEmpty()) { #ifdef THUMBNAIL_HACK if (direct) { // If thumbnail was called directly from Konqueror, then the image needs to be raw //qDebug() << "RAW IMAGE TO STREAM"; QBuffer buf; if (!buf.open(QIODevice::WriteOnly)) { error(KIO::ERR_INTERNAL, i18n("Could not write image.")); return; } img.save(&buf,"PNG"); buf.close(); mimeType("image/png"); data(buf.buffer()); } else #endif { QByteArray imgData; QDataStream stream( &imgData, QIODevice::WriteOnly ); //qDebug() << "IMAGE TO STREAM"; stream << img; mimeType("application/octet-stream"); data(imgData); } } else { #ifndef Q_OS_WIN QByteArray imgData; QDataStream stream( &imgData, QIODevice::WriteOnly ); //qDebug() << "IMAGE TO SHMID"; void *shmaddr = shmat(shmid.toInt(), nullptr, 0); if (shmaddr == (void *)-1) { error(KIO::ERR_INTERNAL, i18n("Failed to attach to shared memory segment %1", shmid)); return; } if (img.width() * img.height() > m_width * m_height) { error(KIO::ERR_INTERNAL, i18n("Image is too big for the shared memory segment")); shmdt((char*)shmaddr); return; } if( img.format() != QImage::Format_ARGB32 ) { // KIO::PreviewJob and this code below completely ignores colortable :-/, img = img.convertToFormat(QImage::Format_ARGB32); // so make sure there is none } // Keep in sync with kdelibs/kio/kio/previewjob.cpp stream << img.width() << img.height() << quint8(img.format()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) memcpy(shmaddr, img.bits(), img.sizeInBytes()); #else memcpy(shmaddr, img.bits(), img.byteCount()); #endif shmdt((char*)shmaddr); mimeType("application/octet-stream"); data(imgData); #endif } finished(); } QString ThumbnailProtocol::pluginForMimeType(const QString& mimeType) { KService::List offers = KMimeTypeTrader::self()->query( mimeType, QLatin1String("ThumbCreator")); if (!offers.isEmpty()) { KService::Ptr serv; serv = offers.first(); return serv->library(); } //Match group mimetypes ///@todo Move this into some central location together with the related matching code in previewjob.cpp. This doesn't handle inheritance and such const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator"); for (const KService::Ptr& plugin : plugins) { const QStringList mimeTypes = plugin->serviceTypes(); for (const QString& mime : mimeTypes) { if(mime.endsWith('*')) { const auto mimeGroup = mime.leftRef(mime.length()-1); if(mimeType.startsWith(mimeGroup)) return plugin->library(); } } } return QString(); } float ThumbnailProtocol::sequenceIndex() const { return metaData("sequence-index").toFloat(); } bool ThumbnailProtocol::isOpaque(const QImage &image) const { // Test the corner pixels return qAlpha(image.pixel(QPoint(0, 0))) == 255 && qAlpha(image.pixel(QPoint(image.width()-1, 0))) == 255 && qAlpha(image.pixel(QPoint(0, image.height()-1))) == 255 && qAlpha(image.pixel(QPoint(image.width()-1, image.height()-1))) == 255; } void ThumbnailProtocol::drawPictureFrame(QPainter *painter, const QPoint ¢erPos, const QImage &image, int frameWidth, QSize imageTargetSize) const { // Scale the image down so it matches the aspect ratio float scaling = 1.0; if ((image.size().width() > imageTargetSize.width()) && (imageTargetSize.width() != 0)) { scaling = float(imageTargetSize.width()) / float(image.size().width()); } QImage frame(imageTargetSize + QSize(frameWidth * 2, frameWidth * 2), QImage::Format_ARGB32); frame.fill(0); float scaledFrameWidth = frameWidth / scaling; QTransform m; m.rotate(qrand() % 17 - 8); // Random rotation ±8° m.scale(scaling, scaling); QRectF frameRect(QPointF(0, 0), QPointF(image.width() + scaledFrameWidth*2, image.height() + scaledFrameWidth*2)); QRect r = m.mapRect(QRectF(frameRect)).toAlignedRect(); QImage transformed(r.size(), QImage::Format_ARGB32); transformed.fill(0); QPainter p(&transformed); p.setRenderHint(QPainter::SmoothPixmapTransform); p.setCompositionMode(QPainter::CompositionMode_Source); p.translate(-r.topLeft()); p.setWorldTransform(m, true); if (isOpaque(image)) { p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(Qt::white); p.drawRoundedRect(frameRect, scaledFrameWidth / 2, scaledFrameWidth / 2); } p.drawImage(scaledFrameWidth, scaledFrameWidth, image); p.end(); int radius = qMax(frameWidth, 1); QImage shadow(r.size() + QSize(radius * 2, radius * 2), QImage::Format_ARGB32); shadow.fill(0); p.begin(&shadow); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawImage(radius, radius, transformed); p.end(); ImageFilter::shadowBlur(shadow, radius, QColor(0, 0, 0, 128)); r.moveCenter(centerPos); painter->drawImage(r.topLeft() - QPoint(radius / 2, radius / 2), shadow); painter->drawImage(r.topLeft(), transformed); } QImage ThumbnailProtocol::thumbForDirectory(const QUrl& directory) { QImage img; if (m_propagationDirectories.isEmpty()) { // Directories that the directory preview will be propagated into if there is no direct sub-directories const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); m_propagationDirectories = globalConfig.readEntry("PropagationDirectories", QStringList() << "VIDEO_TS").toSet(); m_maxFileSize = globalConfig.readEntry("MaximumSize", std::numeric_limits::max()); } const int tiles = 2; //Count of items shown on each dimension const int spacing = 1; const int visibleCount = tiles * tiles; // TODO: the margins are optimized for the Oxygen iconset // Provide a fallback solution for other iconsets (e. g. draw folder // only as small overlay, use no margins) QString localFile = directory.path(); KFileItem item(QUrl::fromLocalFile(localFile)); const int extent = qMin(m_width, m_height); QPixmap folder = QIcon::fromTheme(item.iconName()).pixmap(extent); // Scale up base icon to ensure overlays are rendered with // the best quality possible even for low-res custom folder icons if (qMax(folder.width(), folder.height()) < extent) { folder = folder.scaled(extent, extent, Qt::KeepAspectRatio, Qt::SmoothTransformation); } const int folderWidth = folder.width(); const int folderHeight = folder.height(); const int topMargin = folderHeight * 30 / 100; const int bottomMargin = folderHeight / 6; const int leftMargin = folderWidth / 13; const int rightMargin = leftMargin; const int segmentWidth = (folderWidth - leftMargin - rightMargin + spacing) / tiles - spacing; const int segmentHeight = (folderHeight - topMargin - bottomMargin + spacing) / tiles - spacing; if ((segmentWidth < 5) || (segmentHeight < 5)) { // the segment size is too small for a useful preview return img; } // Multiply with a high number, so we get some semi-random sequence int skipValidItems = ((int)sequenceIndex()) * tiles * tiles; img = QImage(QSize(folderWidth, folderHeight), QImage::Format_ARGB32); img.fill(0); QPainter p; p.begin(&img); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawPixmap(0, 0, folder); p.setCompositionMode(QPainter::CompositionMode_SourceOver); int xPos = leftMargin; int yPos = topMargin; int frameWidth = qRound(folderWidth / 85.); int iterations = 0; QString hadFirstThumbnail; int skipped = 0; const int maxYPos = folderHeight - bottomMargin - segmentHeight; int validThumbnails = 0; while ((skipped <= skipValidItems) && (yPos <= maxYPos) && validThumbnails == 0) { QDirIterator dir(localFile, QDir::Files | QDir::Readable); if (!dir.hasNext()) { break; } while (dir.hasNext() && (yPos <= maxYPos)) { ++iterations; if (iterations > 500) { skipValidItems = skipped = 0; break; } dir.next(); if (validThumbnails > 0 && hadFirstThumbnail == dir.filePath()) { break; // Never show the same thumbnail twice } if (dir.fileInfo().size() > m_maxFileSize) { // don't create thumbnails for files that exceed // the maximum set file size continue; } if (!drawSubThumbnail(p, dir.filePath(), segmentWidth, segmentHeight, xPos, yPos, frameWidth)) { continue; } if (skipped < skipValidItems) { ++skipped; continue; } if (hadFirstThumbnail.isEmpty()) { hadFirstThumbnail = dir.filePath(); } ++validThumbnails; xPos += segmentWidth + spacing; if (xPos > folderWidth - rightMargin - segmentWidth) { xPos = leftMargin; yPos += segmentHeight + spacing; } } if (skipped != 0) { // Round up to full pages const int roundedDown = (skipped / visibleCount) * visibleCount; if (roundedDown < skipped) { skipped = roundedDown + visibleCount; } else { skipped = roundedDown; } } if (skipped == 0) { break; // No valid items were found } // We don't need to iterate again and again: Subtract any multiple of "skipped" from the count we still need to skip skipValidItems -= (skipValidItems / skipped) * skipped; skipped = 0; } p.end(); if (validThumbnails == 0) { // Eventually propagate the contained items from a sub-directory QDirIterator dir(localFile, QDir::Dirs); int max = 50; while (dir.hasNext() && max > 0) { --max; dir.next(); if (m_propagationDirectories.contains(dir.fileName())) { return thumbForDirectory(QUrl(dir.filePath())); } } // If no thumbnail could be found, return an empty image which indicates // that no preview for the directory is available. img = QImage(); } // If only for one file a thumbnail could be generated then paint an image with only one tile if (validThumbnails == 1) { QImage oneTileImg(folder.size(), QImage::Format_ARGB32); oneTileImg.fill(0); QPainter oneTilePainter(&oneTileImg); oneTilePainter.setCompositionMode(QPainter::CompositionMode_Source); oneTilePainter.drawPixmap(0, 0, folder); oneTilePainter.setCompositionMode(QPainter::CompositionMode_SourceOver); const int oneTileWidth = folderWidth - leftMargin - rightMargin; const int oneTileHeight = folderHeight - topMargin - bottomMargin; drawSubThumbnail(oneTilePainter, hadFirstThumbnail, oneTileWidth, oneTileHeight, leftMargin, topMargin, frameWidth); return oneTileImg; } return img; } ThumbCreator* ThumbnailProtocol::getThumbCreator(const QString& plugin) { ThumbCreator *creator = m_creators[plugin]; if (!creator) { // Don't use KPluginFactory here, this is not a QObject and // neither is ThumbCreator QLibrary library(KPluginLoader::findPlugin((plugin))); if (library.load()) { newCreator create = (newCreator)library.resolve("new_creator"); if (create) { creator = create(); } } if (!creator) { return nullptr; } m_creators.insert(plugin, creator); } return creator; } const QImage ThumbnailProtocol::getIcon() { const QMimeDatabase db; ///@todo Can we really do this? It doesn't seem to respect the size if (!m_iconDict.contains(m_mimeType)) { // generate it QImage icon(KIconLoader::global()->loadMimeTypeIcon(db.mimeTypeForName(m_mimeType).iconName(), KIconLoader::Desktop, m_iconSize).toImage()); icon = icon.convertToFormat(QImage::Format_ARGB32); m_iconDict.insert(m_mimeType, icon); return icon; } return m_iconDict.value(m_mimeType); } bool ThumbnailProtocol::createSubThumbnail(QImage& thumbnail, const QString& filePath, int segmentWidth, int segmentHeight) { const QMimeDatabase db; const QUrl fileUrl = QUrl::fromLocalFile(filePath); const QString subPlugin = pluginForMimeType(db.mimeTypeForUrl(fileUrl).name()); if (subPlugin.isEmpty() || !m_enabledPlugins.contains(subPlugin)) { return false; } ThumbCreator* subCreator = getThumbCreator(subPlugin); if (!subCreator) { // qDebug() << "found no creator for" << dir.filePath(); return false; } if ((segmentWidth <= 256) && (segmentHeight <= 256)) { // check whether a cached version of the file is available for // 128 x 128 or 256 x 256 pixels int cacheSize = 0; QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(QFile::encodeName(fileUrl.toString())); const QString thumbName = QFile::encodeName(md5.result().toHex()).append(".png"); if (m_thumbBasePath.isEmpty()) { m_thumbBasePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); QDir basePath(m_thumbBasePath); basePath.mkpath("normal/"); QFile::setPermissions(basePath.absoluteFilePath("normal"), QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); basePath.mkpath("large/"); QFile::setPermissions(basePath.absoluteFilePath("large"), QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); } QDir thumbPath(m_thumbBasePath); if ((segmentWidth <= 128) && (segmentHeight <= 128)) { cacheSize = 128; thumbPath.cd("normal"); } else { cacheSize = 256; thumbPath.cd("large"); } if (!thumbnail.load(thumbPath.absoluteFilePath(thumbName))) { // no cached version is available, a new thumbnail must be created QSaveFile thumbnailfile(thumbPath.absoluteFilePath(thumbName)); bool savedCorrectly = false; if (subCreator->create(filePath, cacheSize, cacheSize, thumbnail)) { scaleDownImage(thumbnail, cacheSize, cacheSize); // The thumbnail has been created successfully. Store the thumbnail // to the cache for future access. if (thumbnailfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { savedCorrectly = thumbnail.save(&thumbnailfile, "PNG"); } } else { return false; } if(savedCorrectly) { thumbnailfile.commit(); } } } else if (!subCreator->create(filePath, segmentWidth, segmentHeight, thumbnail)) { return false; } return true; } void ThumbnailProtocol::scaleDownImage(QImage& img, int maxWidth, int maxHeight) { if (img.width() > maxWidth || img.height() > maxHeight) { img = img.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } } bool ThumbnailProtocol::drawSubThumbnail(QPainter& p, const QString& filePath, int width, int height, int xPos, int yPos, int frameWidth) { QImage subThumbnail; if (!createSubThumbnail(subThumbnail, filePath, width, height)) { return false; } // Seed the random number generator so that it always returns the same result // for the same directory and sequence-item qsrand(qHash(filePath)); // Apply fake smooth scaling, as seen on several blogs if (subThumbnail.width() > width * 4 || subThumbnail.height() > height * 4) { subThumbnail = subThumbnail.scaled(width*4, height*4, Qt::KeepAspectRatio, Qt::FastTransformation); } QSize targetSize(subThumbnail.size()); targetSize.scale(width, height, Qt::KeepAspectRatio); // center the image inside the segment boundaries const QPoint centerPos(xPos + (width/ 2), yPos + (height / 2)); drawPictureFrame(&p, centerPos, subThumbnail, frameWidth, targetSize); return true; }