diff --git a/archive/kio_archive.cpp b/archive/kio_archive.cpp index da54d746..c482afd5 100644 --- a/archive/kio_archive.cpp +++ b/archive/kio_archive.cpp @@ -1,82 +1,82 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 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_archive.h" #include #include #include #include #include #include "kio_archive_debug.h" using namespace KIO; extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv); } int kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName(QLatin1String("kio_archive")); qCDebug(KIO_ARCHIVE_LOG) << "Starting" << QCoreApplication::applicationPid(); if (argc != 4) { fprintf(stderr, "Usage: kio_archive protocol domain-socket1 domain-socket2\n"); exit(-1); } ArchiveProtocol slave( argv[1], argv[2], argv[3]); slave.dispatchLoop(); qCDebug(KIO_ARCHIVE_LOG) << "Done"; return 0; } ArchiveProtocol::ArchiveProtocol( const QByteArray &proto, const QByteArray &pool, const QByteArray &app ) : ArchiveProtocolBase( proto, pool, app ) { qCDebug(KIO_ARCHIVE_LOG); } KArchive *ArchiveProtocol::createArchive( const QString & proto, const QString & archiveFile ) { if ( proto == "ar" ) { qCDebug(KIO_ARCHIVE_LOG) << "Opening KAr on " << archiveFile; return new KAr( archiveFile ); } else if ( proto == "tar" ) { qCDebug(KIO_ARCHIVE_LOG) << "Opening KTar on " << archiveFile; return new KTar( archiveFile ); } else if ( proto == "zip" ) { qCDebug(KIO_ARCHIVE_LOG) << "Opening KZip on " << archiveFile; return new KZip( archiveFile ); } else { qCWarning(KIO_ARCHIVE_LOG) << "Protocol" << proto << "not supported by this IOSlave" ; - return 0L; + return nullptr; } } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/archive/kio_archivebase.cpp b/archive/kio_archivebase.cpp index 8d1f9c38..150cbc41 100644 --- a/archive/kio_archivebase.cpp +++ b/archive/kio_archivebase.cpp @@ -1,589 +1,589 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 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_archivebase.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define S_ISDIR(m) (((m & S_IFMT) == S_IFDIR)) #endif using namespace KIO; ArchiveProtocolBase::ArchiveProtocolBase( const QByteArray &proto, const QByteArray &pool, const QByteArray &app ) : SlaveBase( proto, pool, app ) { qCDebug(KIO_ARCHIVE_LOG); - m_archiveFile = 0L; + m_archiveFile = nullptr; } ArchiveProtocolBase::~ArchiveProtocolBase() { delete m_archiveFile; } bool ArchiveProtocolBase::checkNewFile( const QUrl & url, QString & path, KIO::Error& errorNum ) { #ifndef Q_OS_WIN QString fullPath = url.path(); #else QString fullPath = url.path().remove(0, 1); #endif qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::checkNewFile" << fullPath; // Are we already looking at that file ? if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) ) { // Has it changed ? QT_STATBUF statbuf; if ( QT_STAT( QFile::encodeName( m_archiveName ), &statbuf ) == 0 ) { if ( m_mtime == statbuf.st_mtime ) { path = fullPath.mid( m_archiveName.length() ); qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::checkNewFile returning" << path; return true; } } } qCDebug(KIO_ARCHIVE_LOG) << "Need to open a new file"; // Close previous file if ( m_archiveFile ) { m_archiveFile->close(); delete m_archiveFile; - m_archiveFile = 0L; + m_archiveFile = nullptr; } // Find where the tar file is in the full path int pos = 0; QString archiveFile; path.clear(); int len = fullPath.length(); if ( len != 0 && fullPath[ len - 1 ] != '/' ) fullPath += '/'; qCDebug(KIO_ARCHIVE_LOG) << "the full path is" << fullPath; QT_STATBUF statbuf; statbuf.st_mode = 0; // be sure to clear the directory bit while ( (pos=fullPath.indexOf( '/', pos+1 )) != -1 ) { QString tryPath = fullPath.left( pos ); qCDebug(KIO_ARCHIVE_LOG) << fullPath << "trying" << tryPath; if ( QT_STAT( QFile::encodeName(tryPath), &statbuf ) == -1 ) { // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore break; } if ( !S_ISDIR(statbuf.st_mode) ) { archiveFile = tryPath; m_mtime = statbuf.st_mtime; #ifdef Q_OS_WIN // st_uid and st_gid provides no information m_user.clear(); m_group.clear(); #else KUser user(statbuf.st_uid); m_user = user.loginName(); KUserGroup group(statbuf.st_gid); m_group = group.name(); #endif path = fullPath.mid( pos + 1 ); qCDebug(KIO_ARCHIVE_LOG).nospace() << "fullPath=" << fullPath << " path=" << path; len = path.length(); if ( len > 1 ) { if ( path[ len - 1 ] == '/' ) path.truncate( len - 1 ); } else path = QString::fromLatin1("/"); qCDebug(KIO_ARCHIVE_LOG).nospace() << "Found. archiveFile=" << archiveFile << " path=" << path; break; } } if ( archiveFile.isEmpty() ) { qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::checkNewFile: not found"; if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory? { // Too bad, it is a directory, not an archive. qCDebug(KIO_ARCHIVE_LOG) << "Path is a directory, not an archive."; errorNum = KIO::ERR_IS_DIRECTORY; } else errorNum = KIO::ERR_DOES_NOT_EXIST; return false; } // Open new file m_archiveFile = this->createArchive( url.scheme(), archiveFile ); if ( !m_archiveFile ) { qCWarning(KIO_ARCHIVE_LOG) << "Protocol" << url.scheme() << "not supported by this IOSlave" ; errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL; return false; } if ( !m_archiveFile->open( QIODevice::ReadOnly ) ) { qCDebug(KIO_ARCHIVE_LOG) << "Opening" << archiveFile << "failed."; delete m_archiveFile; - m_archiveFile = 0L; + m_archiveFile = nullptr; errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING; return false; } m_archiveName = archiveFile; return true; } void ArchiveProtocolBase::createRootUDSEntry( KIO::UDSEntry & entry ) { entry.clear(); entry.insert( KIO::UDSEntry::UDS_NAME, "." ); entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, m_mtime ); //entry.insert( KIO::UDSEntry::UDS_ACCESS, 07777 ); // fake 'x' permissions, this is a pseudo-directory entry.insert( KIO::UDSEntry::UDS_USER, m_user); entry.insert( KIO::UDSEntry::UDS_GROUP, m_group); } void ArchiveProtocolBase::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry ) { entry.clear(); entry.insert( KIO::UDSEntry::UDS_NAME, archiveEntry->name() ); entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, archiveEntry->permissions() & S_IFMT ); // keep file type only entry.insert( KIO::UDSEntry::UDS_SIZE, archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L ); entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, archiveEntry->date().toTime_t()); entry.insert( KIO::UDSEntry::UDS_ACCESS, archiveEntry->permissions() & 07777 ); // keep permissions only entry.insert( KIO::UDSEntry::UDS_USER, archiveEntry->user()); entry.insert( KIO::UDSEntry::UDS_GROUP, archiveEntry->group()); entry.insert( KIO::UDSEntry::UDS_LINK_DEST, archiveEntry->symLinkTarget()); } void ArchiveProtocolBase::listDir( const QUrl & url ) { qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::listDir" << url.url(); QString path; KIO::Error errorNum; if ( !checkNewFile( url, path, errorNum ) ) { if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) { // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) // Therefore give a more specific error message error( KIO::ERR_SLAVE_DEFINED, i18n( "Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString() ) ); return; } else if ( errorNum != ERR_IS_DIRECTORY ) { // We have any other error error( errorNum, url.toDisplayString() ); return; } // It's a real dir -> redirect QUrl redir; redir.setPath( url.path() ); qCDebug(KIO_ARCHIVE_LOG) << "Ok, redirection to" << redir.url(); redirection( redir ); finished(); // And let go of the tar file - for people who want to unmount a cdrom after that delete m_archiveFile; - m_archiveFile = 0L; + m_archiveFile = nullptr; return; } if ( path.isEmpty() ) { QUrl redir( url.scheme() + QString::fromLatin1( ":/") ); qCDebug(KIO_ARCHIVE_LOG) << "url.path()=" << url.path(); redir.setPath( url.path() + QString::fromLatin1("/") ); qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::listDir: redirection" << redir.url(); redirection( redir ); finished(); return; } qCDebug(KIO_ARCHIVE_LOG) << "checkNewFile done"; const KArchiveDirectory* root = m_archiveFile->directory(); const KArchiveDirectory* dir; if (!path.isEmpty() && path != "/") { qCDebug(KIO_ARCHIVE_LOG) << "Looking for entry" << path; const KArchiveEntry* e = root->entry( path ); if ( !e ) { error( KIO::ERR_DOES_NOT_EXIST, url.toDisplayString() ); return; } if ( ! e->isDirectory() ) { error( KIO::ERR_IS_FILE, url.toDisplayString() ); return; } dir = (KArchiveDirectory*)e; } else { dir = root; } const QStringList l = dir->entries(); totalSize( l.count() ); UDSEntry entry; if (!l.contains(".")) { createRootUDSEntry(entry); listEntry(entry); } QStringList::const_iterator it = l.begin(); for( ; it != l.end(); ++it ) { qCDebug(KIO_ARCHIVE_LOG) << (*it); const KArchiveEntry* archiveEntry = dir->entry( (*it) ); createUDSEntry( archiveEntry, entry ); listEntry(entry); } finished(); qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::listDir done"; } void ArchiveProtocolBase::stat( const QUrl & url ) { QString path; UDSEntry entry; KIO::Error errorNum; if ( !checkNewFile( url, path, errorNum ) ) { // We may be looking at a real directory - this happens // when pressing up after being in the root of an archive if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) { // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) // Therefore give a more specific error message error( KIO::ERR_SLAVE_DEFINED, i18n( "Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString() ) ); return; } else if ( errorNum != ERR_IS_DIRECTORY ) { // We have any other error error( errorNum, url.toDisplayString() ); return; } // Real directory. Return just enough information for KRun to work entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName()); qCDebug(KIO_ARCHIVE_LOG).nospace() << "ArchiveProtocolBase::stat returning name=" << url.fileName(); QT_STATBUF buff; #ifdef Q_OS_WIN QString fullPath = url.path().remove(0, 1); #else QString fullPath = url.path(); #endif if ( QT_STAT( QFile::encodeName( fullPath ), &buff ) == -1 ) { // Should not happen, as the file was already stated by checkNewFile error( KIO::ERR_COULD_NOT_STAT, url.toDisplayString() ); return; } entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT); statEntry( entry ); finished(); // And let go of the tar file - for people who want to unmount a cdrom after that delete m_archiveFile; - m_archiveFile = 0L; + m_archiveFile = nullptr; return; } const KArchiveDirectory* root = m_archiveFile->directory(); const KArchiveEntry* archiveEntry; if ( path.isEmpty() ) { path = QString::fromLatin1( "/" ); archiveEntry = root; } else { archiveEntry = root->entry( path ); } if ( !archiveEntry ) { error( KIO::ERR_DOES_NOT_EXIST, url.toDisplayString() ); return; } createUDSEntry( archiveEntry, entry ); statEntry( entry ); finished(); } void ArchiveProtocolBase::get( const QUrl & url ) { qCDebug(KIO_ARCHIVE_LOG) << "ArchiveProtocolBase::get" << url.url(); QString path; KIO::Error errorNum; if ( !checkNewFile( url, path, errorNum ) ) { if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) { // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) // Therefore give a more specific error message error( KIO::ERR_SLAVE_DEFINED, i18n( "Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString() ) ); return; } else { // We have any other error error( errorNum, url.toDisplayString() ); return; } } const KArchiveDirectory* root = m_archiveFile->directory(); const KArchiveEntry* archiveEntry = root->entry( path ); if ( !archiveEntry ) { error( KIO::ERR_DOES_NOT_EXIST, url.toDisplayString() ); return; } if ( archiveEntry->isDirectory() ) { error( KIO::ERR_IS_DIRECTORY, url.toDisplayString() ); return; } const KArchiveFile* archiveFileEntry = static_cast(archiveEntry); if ( !archiveEntry->symLinkTarget().isEmpty() ) { const QString target = archiveEntry->symLinkTarget(); qCDebug(KIO_ARCHIVE_LOG) << "Redirection to" << target; const QUrl realURL = url.resolved(QUrl(target)); qCDebug(KIO_ARCHIVE_LOG) << "realURL=" << realURL; redirection( realURL ); finished(); return; } //qCDebug(KIO_ARCHIVE_LOG) << "Preparing to get the archive data"; /* * The easy way would be to get the data by calling archiveFileEntry->data() * However this has drawbacks: * - the complete file must be read into the memory * - errors are skipped, resulting in an empty file */ QIODevice* io = archiveFileEntry->createDevice(); if (!io) { error( KIO::ERR_SLAVE_DEFINED, i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" , url.toDisplayString() ) ); return; } if ( !io->open( QIODevice::ReadOnly ) ) { error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString() ); delete io; return; } totalSize( archiveFileEntry->size() ); // Size of a QIODevice read. It must be large enough so that the mime type check will not fail const qint64 maxSize = 0x100000; // 1MB qint64 bufferSize = qMin( maxSize, archiveFileEntry->size() ); QByteArray buffer; buffer.resize( bufferSize ); if ( buffer.isEmpty() && bufferSize > 0 ) { // Something went wrong error( KIO::ERR_OUT_OF_MEMORY, url.toDisplayString() ); delete io; return; } bool firstRead = true; // How much file do we still have to process? qint64 fileSize = archiveFileEntry->size(); KIO::filesize_t processed = 0; while ( !io->atEnd() && fileSize > 0 ) { if ( !firstRead ) { bufferSize = qMin( maxSize, fileSize ); buffer.resize( bufferSize ); } const qint64 read = io->read( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong. if ( read != bufferSize ) { qCWarning(KIO_ARCHIVE_LOG) << "Read" << read << "bytes but expected" << bufferSize ; error( KIO::ERR_COULD_NOT_READ, url.toDisplayString() ); delete io; return; } if ( firstRead ) { // We use the magic one the first data read // (As magic detection is about fixed positions, we can be sure that it is enough data.) QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData( path, buffer ); qCDebug(KIO_ARCHIVE_LOG) << "Emitting mimetype" << mime.name(); mimeType( mime.name() ); firstRead = false; } data( buffer ); processed += read; processedSize( processed ); fileSize -= bufferSize; } io->close(); delete io; data( QByteArray() ); finished(); } /* In case someone wonders how the old filter stuff looked like : :) void TARProtocol::slotData(void *_p, int _len) { switch (m_cmd) { case CMD_PUT: assert(m_pFilter); m_pFilter->send(_p, _len); break; default: abort(); break; } } void TARProtocol::slotDataEnd() { switch (m_cmd) { case CMD_PUT: assert(m_pFilter && m_pJob); m_pFilter->finish(); m_pJob->dataEnd(); m_cmd = CMD_NONE; break; default: abort(); break; } } void TARProtocol::jobData(void *_p, int _len) { switch (m_cmd) { case CMD_GET: assert(m_pFilter); m_pFilter->send(_p, _len); break; case CMD_COPY: assert(m_pFilter); m_pFilter->send(_p, _len); break; default: abort(); } } void TARProtocol::jobDataEnd() { switch (m_cmd) { case CMD_GET: assert(m_pFilter); m_pFilter->finish(); dataEnd(); break; case CMD_COPY: assert(m_pFilter); m_pFilter->finish(); m_pJob->dataEnd(); break; default: abort(); } } void TARProtocol::filterData(void *_p, int _len) { debug("void TARProtocol::filterData"); switch (m_cmd) { case CMD_GET: data(_p, _len); break; case CMD_PUT: assert (m_pJob); m_pJob->data(_p, _len); break; case CMD_COPY: assert(m_pJob); m_pJob->data(_p, _len); break; default: abort(); } } */ // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/fish/fish.cpp b/fish/fish.cpp index 022a8906..315650a0 100644 --- a/fish/fish.cpp +++ b/fish/fish.cpp @@ -1,1713 +1,1713 @@ /*************************************************************************** fish.cpp - a FISH kioslave ------------------- begin : Thu Oct 4 17:09:14 CEST 2001 copyright : (C) 2001-2003 by Jörg Walter email : jwalt-kde@garni.ch ***************************************************************************/ /*************************************************************************** * * * 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, version 2 of the License * * * ***************************************************************************/ /* This code contains fragments and ideas from the ftp kioslave done by David Faure . Structure is a bit complicated, since I made the mistake to use KProcess... now there is a lightweight homebrew async IO system inside, but if signals/slots become available for ioslaves, switching back to KProcess should be easy. */ #include "fish.h" #include #include "config-fish.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTY_H #include #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_UTIL_H #include #endif #ifndef Q_OS_WIN #include #endif #include #include #include #include "fishcode.h" #include "loggingcategory.h" #ifndef NDEBUG #define myDebug(x) qCDebug(KIO_FISH_DEBUG) << __LINE__ << ": " x #define connected() do{myDebug( << "_______ emitting connected()"); connected();}while(0) #define dataReq() do{myDebug( << "_______ emitting dataReq()"); dataReq();}while(0) #define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()"); needSubURLData();}while(0) #define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")"); slaveStatus(x,y);}while(0) #define statEntry(x) do{myDebug( << "_______ emitting statEntry("<encode(x).data()) using namespace KIO; extern "C" { int Q_DECL_EXPORT kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName("kio_fish"); myDebug( << "*** Starting fish "); if (argc != 4) { myDebug( << "Usage: kio_fish protocol domain-socket1 domain-socket2"); exit(-1); } setenv("TZ", "UTC", true); fishProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); myDebug( << "*** fish Done"); return 0; } } const struct fishProtocol::fish_info fishProtocol::fishInfo[] = { { ("FISH"), 0, ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while() { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"), 1 }, { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0, ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"), 1 }, { ("PWD"), 0, ("pwd"), 1 }, { ("LIST"), 1, ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), 0 }, { ("STAT"), 1, ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), 0 }, { ("RETR"), 1, ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"), 1 }, { ("STOR"), 2, ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;" "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"), 0 }, { ("CWD"), 1, ("cd %1"), 0 }, { ("CHMOD"), 2, ("chmod %1 %2"), 0 }, { ("DELE"), 1, ("rm -f %1"), 0 }, { ("MKD"), 1, ("mkdir %1"), 0 }, { ("RMD"), 1, ("rmdir %1"), 0 }, { ("RENAME"), 2, ("mv -f %1 %2"), 0 }, { ("LINK"), 2, ("ln -f %1 %2"), 0 }, { ("SYMLINK"), 2, ("ln -sf %1 %2"), 0 }, { ("CHOWN"), 2, ("chown %1 %2"), 0 }, { ("CHGRP"), 2, ("chgrp %1 %2"), 0 }, { ("READ"), 3, ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;" "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;" "dd bs=%2 count=1; ) 2>/dev/null;"), 0 }, // Yes, this is "ibs=1", since dd "count" is input blocks. // On network connections, read() may not fill the buffer // completely (no more data immediately available), but dd // does ignore that fact by design. Sorry, writes are slow. // OTOH, WRITE is not used by the current ioslave methods, // we use APPEND. { ("WRITE"), 3, (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | " "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"), 0 }, { ("COPY"), 2, ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"), 0 }, { ("APPEND"), 2, (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"), 0 }, { ("EXEC"), 2, ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"), 0 } }; fishProtocol::fishProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024, '\0'), mimeTypeSent(false) { myDebug( << "fishProtocol::fishProtocol()"); - if (sshPath == NULL) { + if (sshPath == nullptr) { // disabled: currently not needed. Didn't work reliably. // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null"); #ifdef Q_OS_WIN sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("plink"))); #else sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("ssh"))); #endif } - if (suPath == NULL) { + if (suPath == nullptr) { suPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("su"))); } childPid = 0; connectionPort = 0; isLoggedIn = false; writeReady = true; isRunning = false; firstLogin = true; errorCount = 0; rawRead = 0; rawWrite = -1; recvLen = -1; sendLen = -1; connectionAuth.keepPassword = true; connectionAuth.url.setScheme("fish"); outBufPos = -1; - outBuf = NULL; + outBuf = nullptr; outBufLen = 0; udsType = 0; hasAppend = false; isStat = false; // FIXME: just a workaround for konq deficiencies redirectUser = ""; // FIXME: just a workaround for konq deficiencies redirectPass = ""; // FIXME: just a workaround for konq deficiencies fishCodeLen = strlen(fishCode); } /* ---------------------------------------------------------------------------------- */ fishProtocol::~fishProtocol() { myDebug( << "fishProtocol::~fishProtocol()"); shutdownConnection(true); } /* --------------------------------------------------------------------------- */ /** Connects to a server and logs us in via SSH. Then starts FISH protocol. */ void fishProtocol::openConnection() { if (childPid) return; if (connectionHost.isEmpty()) { error( KIO::ERR_UNKNOWN_HOST, QString() ); return; } infoMessage(i18n("Connecting...")); myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort); sendCommand(FISH_FISH); sendCommand(FISH_VER); if (connectionStart()) { error(ERR_COULD_NOT_CONNECT,connectionHost); shutdownConnection(); return; }; myDebug( << "subprocess is running"); } // XXX Use KPty! XXX #ifndef Q_OS_WIN static int open_pty_pair(int fd[2]) { #if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY) /** with kind regards to The GNU C Library Reference Manual for Version 2.2.x of the GNU C Library */ int master, slave; char *name; struct ::termios ti; memset(&ti,0,sizeof(ti)); ti.c_cflag = CLOCAL|CREAD|CS8; ti.c_cc[VMIN] = 1; #ifdef HAVE_GETPT master = getpt(); #else master = open("/dev/ptmx", O_RDWR); #endif if (master < 0) return 0; if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master; name = ptsname(master); if (name == NULL) goto close_master; slave = open(name, O_RDWR); if (slave == -1) goto close_master; #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) if (isastream(slave) && (ioctl(slave, I_PUSH, "ptem") < 0 || ioctl(slave, I_PUSH, "ldterm") < 0)) goto close_slave; #endif tcsetattr(slave, TCSANOW, &ti); fd[0] = master; fd[1] = slave; return 0; #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) close_slave: #endif close(slave); close_master: close(master); return -1; #else #ifdef HAVE_OPENPTY struct ::termios ti; memset(&ti,0,sizeof(ti)); ti.c_cflag = CLOCAL|CREAD|CS8; ti.c_cc[VMIN] = 1; - return openpty(fd,fd+1,NULL,&ti,NULL); + return openpty(fd,fd+1,nullptr,&ti,nullptr); #else #ifdef __GNUC__ #warning "No tty support available. Password dialog won't work." #endif return socketpair(PF_UNIX,SOCK_STREAM,0,fd); #endif #endif } #endif /** creates the subprocess */ bool fishProtocol::connectionStart() { int fd[2]; int rc, flags; thisFn.clear(); #ifndef Q_OS_WIN rc = open_pty_pair(fd); if (rc == -1) { myDebug( << "socketpair failed, error: " << strerror(errno)); return true; } #endif if (!requestNetwork()) return true; myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser); #ifdef Q_OS_WIN childPid = new KProcess(); childPid->setOutputChannelMode(KProcess::MergedChannels); QStringList common_args; common_args << "-l" << connectionUser.toLatin1().constData() << "-x" << connectionHost.toLatin1().constData(); common_args << "echo;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\""; childPid->setProgram(sshPath, common_args); childPid->start(); QByteArray buf; int offset = 0; while (!isLoggedIn) { if (outBuf.size()) { rc = childPid->write(outBuf); outBuf.clear(); } else rc = 0; if(rc < 0) { myDebug( << "write failed, rc: " << rc); outBufPos = -1; //return true; } if (childPid->waitForReadyRead(1000)) { QByteArray buf2 = childPid->readAll(); buf += buf2; int noff = establishConnection(buf); if (noff < 0) return false; if (noff > 0) buf = buf.mid(/*offset+*/noff); // offset = noff; } } #else childPid = fork(); if (childPid == -1) { myDebug( << "fork failed, error: " << strerror(errno)); ::close(fd[0]); ::close(fd[1]); childPid = 0; dropNetwork(); return true; } if (childPid == 0) { // taken from konsole, see TEPty.C for details // note: if we're running on socket pairs, // this will fail, but thats what we expect for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL); struct rlimit rlp; getrlimit(RLIMIT_NOFILE, &rlp); for (int i = 0; i < (int)rlp.rlim_cur; i++) if (i != fd[1]) ::close(i); dup2(fd[1],0); dup2(fd[1],1); dup2(fd[1],2); if (fd[1] > 2) ::close(fd[1]); setsid(); #if defined(TIOCSCTTY) ioctl(0, TIOCSCTTY, 0); #endif int pgrp = getpid(); #if defined( _AIX) || defined( __hpux) tcsetpgrp(0, pgrp); #else ioctl(0, TIOCSPGRP, (char *)&pgrp); #endif const char *dev = ttyname(0); setpgid(0,0); if (dev) ::close(::open(dev, O_WRONLY, 0)); setpgid(0,0); if (local) { - execl(suPath, "su", "-", connectionUser.toLatin1().constData(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0); + execl(suPath, "su", "-", connectionUser.toLatin1().constData(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)nullptr); } else { #define common_args "-l", connectionUser.toLatin1().constData(), "-x", "-e", "none", \ "-q", connectionHost.toLatin1().constData(), \ "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0 // disabled: leave compression up to the client. // (isOpenSSH?"-C":"+C"), if (connectionPort) execl(sshPath, "ssh", "-p", qPrintable(QString::number(connectionPort)), common_args); else execl(sshPath, "ssh", common_args); #undef common_args } myDebug( << "could not exec! " << strerror(errno)); ::exit(-1); } ::close(fd[1]); rc = fcntl(fd[0],F_GETFL,&flags); rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK); childFd = fd[0]; fd_set rfds, wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); char buf[32768]; int offset = 0; while (!isLoggedIn) { FD_SET(childFd,&rfds); FD_ZERO(&wfds); if (outBufPos >= 0) FD_SET(childFd,&wfds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 1000; - rc = select(childFd+1, &rfds, &wfds, NULL, &timeout); + rc = select(childFd+1, &rfds, &wfds, nullptr, &timeout); if (rc < 0) { if (errno == EINTR) continue; myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno)); return true; } if (FD_ISSET(childFd, &wfds) && outBufPos >= 0) { if (outBuf) rc = ::write(childFd, outBuf + outBufPos, outBufLen - outBufPos); else rc = 0; if (rc >= 0) outBufPos += rc; else { if (errno == EINTR) continue; myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno)); outBufPos = -1; //return true; } if (outBufPos >= outBufLen) { outBufPos = -1; - outBuf = NULL; + outBuf = nullptr; outBufLen = 0; } } else if (FD_ISSET(childFd,&rfds)) { rc = ::read(childFd, buf + offset, sizeof(buf) - offset); if (rc > 0) { int noff = establishConnection(buf, rc + offset); if (noff < 0) return false; if (noff > 0) memmove(buf, buf + offset + rc - noff, noff); offset = noff; } else { if (errno == EINTR) continue; myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno)); return true; } } } #endif return false; } /** writes one chunk of data to stdin of child process */ #ifndef Q_OS_WIN void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len) { if (outBufPos >= 0 && outBuf) { #else void fishProtocol::writeChild(const QByteArray &buf, KIO::fileoffset_t len) { if (outBufPos >= 0 && outBuf.size()) { #endif #if 0 QString debug = QString::fromLatin1(outBuf,outBufLen); if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)"); #endif return; } outBuf = buf; outBufPos = 0; outBufLen = len; } /** manages initial communication setup including password queries */ #ifndef Q_OS_WIN int fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len) { QString buf = QString::fromLatin1(buffer,len); #else int fishProtocol::establishConnection(const QByteArray &buffer) { QString buf = buffer; #endif int pos=0; // Strip trailing whitespace while (buf.length() && (buf[buf.length()-1] == ' ')) buf.truncate(buf.length()-1); myDebug( << "establishing: got " << buf); while (childPid && ((pos = buf.indexOf('\n')) >= 0 || buf.endsWith(':') || buf.endsWith('?'))) { pos++; QString str = buf.left(pos); buf = buf.mid(pos); if (str == "\n") continue; if (str == "FISH:\n") { thisFn.clear(); infoMessage(i18n("Initiating protocol...")); if (!connectionAuth.password.isEmpty()) { connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1); cacheAuthentication(connectionAuth); } isLoggedIn = true; return 0; } else if (!str.isEmpty()) { thisFn += str; } else if (buf.endsWith(':')) { if (!redirectUser.isEmpty() && connectionUser != redirectUser) { QUrl dest = url; dest.setUserName(redirectUser); dest.setPassword(redirectPass); redirection(dest); commandList.clear(); commandCodes.clear(); finished(); redirectUser = ""; redirectPass = ""; return -1; } else if (!connectionPassword.isEmpty()) { myDebug( << "sending cpass"); connectionAuth.password = connectionPassword+ENDLINE; connectionPassword.clear(); // su does not like receiving a password directly after sending // the password prompt so we wait a while. if (local) sleep(1); writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length()); } else { myDebug( << "sending mpass"); connectionAuth.prompt = thisFn+buf; if (local) connectionAuth.caption = i18n("Local Login"); else connectionAuth.caption = i18n("SSH Authentication"); if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) { connectionAuth.password.clear(); // don't prefill int errorCode = openPasswordDialogV2(connectionAuth); if (errorCode != 0) { error(errorCode, connectionHost); shutdownConnection(); return -1; } } firstLogin = false; connectionAuth.password += ENDLINE; if (connectionAuth.username != connectionUser) { QUrl dest = url; dest.setUserName(connectionAuth.username); dest.setPassword(connectionAuth.password); redirection(dest); if (isStat) { // FIXME: just a workaround for konq deficiencies redirectUser = connectionAuth.username; redirectPass = connectionAuth.password; } commandList.clear(); commandCodes.clear(); finished(); return -1; } myDebug( << "sending pass"); if (local) sleep(1); writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length()); } thisFn.clear(); #ifdef Q_OS_WIN return buf.length(); } #else return 0; } else if (buf.endsWith('?')) { int rc = messageBox(QuestionYesNo,thisFn+buf); if (rc == KMessageBox::Yes) { writeChild("yes\n",4); } else { writeChild("no\n",3); } thisFn.clear(); return 0; } #endif else { myDebug( << "unmatched case in initial handling! should not happen!"); } #ifdef Q_OS_WIN if (buf.endsWith(QLatin1String("(y/n)"))) { int rc = messageBox(QuestionYesNo,thisFn+buf); if (rc == KMessageBox::Yes) { writeChild("y\n",2); } else { writeChild("n\n",2); } thisFn.clear(); return 0; } #endif } return buf.length(); } void fishProtocol::setHostInternal(const QUrl & u){ int port = u.port(); if(port <= 0 ) // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that. port = 0; setHost(u.host(),port,u.userName(),u.password()); } /** sets connection information for subsequent commands */ void fishProtocol::setHost(const QString & host, quint16 port, const QString & u, const QString & pass){ QString user(u); local = (host == "localhost" && port == 0); if (user.isEmpty()) user = getenv("LOGNAME"); if (host == connectionHost && port == connectionPort && user == connectionUser) return; myDebug( << "setHost " << u << "@" << host); if (childPid) shutdownConnection(); connectionHost = host; connectionAuth.url.setHost(host); connectionUser = user; connectionAuth.username = user; connectionAuth.url.setUserName(user); connectionPort = port; connectionPassword = pass; firstLogin = true; } /** Forced close of the connection This function gets called from the application side of the universe, it shouldn't send any response. */ void fishProtocol::closeConnection(){ myDebug( << "closeConnection()"); shutdownConnection(true); } /** Closes the connection */ void fishProtocol::shutdownConnection(bool forced){ if (childPid) { #ifdef Q_OS_WIN childPid->terminate(); #else int killStatus = kill(childPid,SIGTERM); // We may not have permission... - if (killStatus == 0) waitpid(childPid, 0, 0); + if (killStatus == 0) waitpid(childPid, nullptr, 0); #endif childPid = 0; #ifndef Q_OS_WIN ::close(childFd); // ...in which case this should do the trick childFd = -1; #endif if (!forced) { dropNetwork(); infoMessage(i18n("Disconnected.")); } } outBufPos = -1; - outBuf = NULL; + outBuf = nullptr; outBufLen = 0; qlist.clear(); commandList.clear(); commandCodes.clear(); isLoggedIn = false; writeReady = true; isRunning = false; rawRead = 0; rawWrite = -1; recvLen = -1; sendLen = -1; } /** builds each FISH request and sets the error counter */ bool fishProtocol::sendCommand(fish_command_type cmd, ...) { const fish_info &info = fishInfo[cmd]; myDebug( << "queuing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines); va_list list; va_start(list, cmd); QString realCmd = info.command; QString realAlt = info.alt; static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); for (int i = 0; i < info.params; i++) { QString arg(va_arg(list, const char *)); int pos = -2; while ((pos = rx.indexIn(arg,pos+2)) >= 0) { arg.replace(pos,0,QString("\\")); } //myDebug( << "arg " << i << ": " << arg); realCmd.append(" ").append(arg); realAlt.replace(QRegExp('%'+QString::number(i+1)),arg); } QString s("#"); s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n"); if (realCmd == "FISH") s.prepend(" "); commandList.append(s); commandCodes.append(cmd); va_end(list); return true; } /** checks response string for result code, converting 000 and 001 appropriately */ int fishProtocol::handleResponse(const QString &str){ myDebug( << "handling: " << str); if (str.startsWith(QLatin1String("### "))) { bool isOk = false; int result = str.mid(4,3).toInt(&isOk); if (!isOk) result = 500; if (result == 0) result = (errorCount != 0?500:200); if (result == 1) result = (errorCount != 0?500:100); myDebug( << "result: " << result << ", errorCount: " << errorCount); return result; } else { errorCount++; return 0; } } int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr) { QDateTime dt(QDateTime::currentDateTime().toUTC()); int year = dt.date().year(); int month = dt.date().month(); int currentMonth = month; int day = dayStr.toInt(); static const char * const monthNames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) { month = i+1; break; } int pos = timeyearStr.indexOf(':'); if (timeyearStr.length() == 4 && pos == -1) { year = timeyearStr.toInt(); } else if (pos == -1) { return 0; } else { if (month > currentMonth + 1) year--; dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0); } dt.date().setDate(year,month,day); return dt.toTime_t(); } /** parses response from server and acts accordingly */ void fishProtocol::manageConnection(const QString &l) { QString line(l); int rc = handleResponse(line); QDateTime dt; long pos, pos2, pos3; bool isOk = false; if (!rc) { switch (fishCommand) { case FISH_VER: if (line.startsWith(QLatin1String("VER 0.0.3"))) { line.append(" "); hasAppend = line.contains(" append "); } else { error(ERR_UNSUPPORTED_PROTOCOL,line); shutdownConnection(); } break; case FISH_PWD: url.setPath(line); redirection(url); break; case FISH_LIST: myDebug( << "listReason: " << static_cast(listReason)); /* Fall through */ case FISH_STAT: if (line.length() > 0) { switch (line[0].cell()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { long long val = line.toLongLong(&isOk); if (val > 0 && isOk) errorCount--; if ((fishCommand == FISH_LIST) && (listReason == LIST)) totalSize(val); } break; case 'P': { errorCount--; if (line[1] == 'd') { udsMime = "inode/directory"; udsType = S_IFDIR; } else { if (line[1] == '-') { udsType = S_IFREG; } else if (line[1] == 'l') { udsType = S_IFLNK; } else if (line[1] == 'c') { udsType = S_IFCHR; } else if (line[1] == 'b') { udsType = S_IFBLK; } else if (line[1] == 's') { udsType = S_IFSOCK; } else if (line[1] == 'p') { udsType = S_IFIFO; } else { myDebug( << "unknown file type: " << line[1].cell()); errorCount++; break; } } //myDebug( << "file type: " << udsType); long long accessVal = 0; if (line[2] == 'r') accessVal |= S_IRUSR; if (line[3] == 'w') accessVal |= S_IWUSR; if (line[4] == 'x' || line[4] == 's') accessVal |= S_IXUSR; if (line[4] == 'S' || line[4] == 's') accessVal |= S_ISUID; if (line[5] == 'r') accessVal |= S_IRGRP; if (line[6] == 'w') accessVal |= S_IWGRP; if (line[7] == 'x' || line[7] == 's') accessVal |= S_IXGRP; if (line[7] == 'S' || line[7] == 's') accessVal |= S_ISGID; if (line[8] == 'r') accessVal |= S_IROTH; if (line[9] == 'w') accessVal |= S_IWOTH; if (line[10] == 'x' || line[10] == 't') accessVal |= S_IXOTH; if (line[10] == 'T' || line[10] == 't') accessVal |= S_ISVTX; udsEntry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal); pos = line.indexOf(':',12); if (pos < 0) { errorCount++; break; } udsEntry.insert(KIO::UDSEntry::UDS_USER, line.mid(12,pos-12)); udsEntry.insert(KIO::UDSEntry::UDS_GROUP, line.mid(pos+1)); } break; case 'd': pos = line.indexOf(' '); pos2 = line.indexOf(' ',pos+1); if (pos < 0 || pos2 < 0) break; errorCount--; udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1))); break; case 'D': pos = line.indexOf(' '); pos2 = line.indexOf(' ',pos+1); pos3 = line.indexOf(' ',pos2+1); if (pos < 0 || pos2 < 0 || pos3 < 0) break; dt.setDate(QDate(line.mid(1,pos-1).toInt(),line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt())); pos = pos3; pos2 = line.indexOf(' ',pos+1); pos3 = line.indexOf(' ',pos2+1); if (pos < 0 || pos2 < 0 || pos3 < 0) break; dt.setTime(QTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt())); errorCount--; udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toTime_t()); break; case 'S': { long long sizeVal = line.mid(1).toLongLong(&isOk); if (!isOk) break; errorCount--; udsEntry.insert(KIO::UDSEntry::UDS_SIZE, sizeVal); } break; case 'E': errorCount--; break; case ':': pos = line.lastIndexOf('/'); thisFn = line.mid(pos < 0?1:pos+1); if (fishCommand == FISH_LIST) { udsEntry.insert(KIO::UDSEntry::UDS_NAME, thisFn); } // By default, the mimetype comes from the extension // We'll use the file(1) result only as fallback [like the rest of KDE does] { QUrl kurl("fish://host/"+thisFn); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(kurl); if ( !mime.isDefault() ) udsMime = mime.name(); } errorCount--; break; case 'M': // This is getting ugly. file(1) makes some uneducated // guesses, so we must try to ignore them (#51274) if (udsMime.isEmpty() && line.right(8) != "/unknown" && (thisFn.indexOf('.') < 0 || (line.left(8) != "Mtext/x-" && line != "Mtext/plain"))) { udsMime = line.mid(1); if ( udsMime == "inode/directory" ) // a symlink to a dir is a dir udsType = S_IFDIR; } errorCount--; break; case 'L': udsEntry.insert(KIO::UDSEntry::UDS_LINK_DEST, line.mid(1)); if (!udsType) udsType = S_IFLNK; errorCount--; break; } } else { if (!udsMime.isNull()) udsEntry.insert(KIO::UDSEntry::UDS_MIME_TYPE, udsMime); udsMime.clear(); udsEntry.insert( KIO::UDSEntry::UDS_FILE_TYPE, udsType ); udsType = 0; if (fishCommand == FISH_STAT) udsStatEntry = udsEntry; else if (listReason == LIST) { listEntry(udsEntry); //1 } else if (listReason == CHECK) checkExist = true; //0 errorCount--; udsEntry.clear(); } break; case FISH_RETR: if (line.length() == 0) { error(ERR_IS_DIRECTORY,url.toDisplayString()); recvLen = 0; break; } recvLen = line.toLongLong(&isOk); if (!isOk) { error(ERR_COULD_NOT_READ,url.toDisplayString()); shutdownConnection(); break; } break; default : break; } } else if (rc == 100) { switch (fishCommand) { case FISH_FISH: writeChild(fishCode, fishCodeLen); break; case FISH_READ: recvLen = 1024; /* fall through */ case FISH_RETR: myDebug( << "reading " << recvLen); if (recvLen == -1) { error(ERR_COULD_NOT_READ,url.toDisplayString()); shutdownConnection(); } else { rawRead = recvLen; dataRead = 0; mimeTypeSent = false; if (recvLen == 0) { mimeType("application/x-zerosize"); mimeTypeSent = true; } } break; case FISH_STOR: case FISH_WRITE: case FISH_APPEND: rawWrite = sendLen; //myDebug( << "sending " << sendLen); - writeChild(NULL,0); + writeChild(nullptr,0); break; default : break; } } else if (rc/100 != 2) { switch (fishCommand) { case FISH_STOR: case FISH_WRITE: case FISH_APPEND: error(ERR_COULD_NOT_WRITE,url.toDisplayString()); shutdownConnection(); break; case FISH_RETR: error(ERR_COULD_NOT_READ,url.toDisplayString()); shutdownConnection(); break; case FISH_READ: if ( rc == 501 ) { mimeType("inode/directory"); mimeTypeSent = true; recvLen = 0; finished(); } else { error(ERR_COULD_NOT_READ,url.toDisplayString()); shutdownConnection(); } break; case FISH_FISH: case FISH_VER: error(ERR_SLAVE_DEFINED,line); shutdownConnection(); break; case FISH_PWD: case FISH_CWD: error(ERR_CANNOT_ENTER_DIRECTORY,url.toDisplayString()); break; case FISH_LIST: myDebug( << "list error. reason: " << static_cast(listReason)); if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.toDisplayString()); else if (listReason == CHECK) { checkExist = false; finished(); } break; case FISH_STAT: error(ERR_DOES_NOT_EXIST,url.toDisplayString()); udsStatEntry.clear(); break; case FISH_CHMOD: error(ERR_CANNOT_CHMOD,url.toDisplayString()); break; case FISH_CHOWN: case FISH_CHGRP: error(ERR_ACCESS_DENIED,url.toDisplayString()); break; case FISH_MKD: if ( rc == 501 ) error(ERR_DIR_ALREADY_EXIST,url.toDisplayString()); else error(ERR_COULD_NOT_MKDIR,url.toDisplayString()); break; case FISH_RMD: error(ERR_COULD_NOT_RMDIR,url.toDisplayString()); break; case FISH_DELE: error(ERR_CANNOT_DELETE,url.toDisplayString()); break; case FISH_RENAME: error(ERR_CANNOT_RENAME,url.toDisplayString()); break; case FISH_COPY: case FISH_LINK: case FISH_SYMLINK: error(ERR_COULD_NOT_WRITE,url.toDisplayString()); break; default : break; } } else { if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE); if (fishCommand == FISH_FISH) { connected(); } else if (fishCommand == FISH_LIST) { if (listReason == CHECK && !checkOverwrite && checkExist) { error(ERR_FILE_ALREADY_EXIST,url.toDisplayString()); return; // Don't call finished! } } else if (fishCommand == FISH_STAT) { udsStatEntry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() ); statEntry(udsStatEntry); } else if (fishCommand == FISH_APPEND) { dataReq(); if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(QString::number(rawData.size())),E(url.path())); else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); sendLen = rawData.size(); } else if (fishCommand == FISH_WRITE) { dataReq(); if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path())); else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); putPos += rawData.size(); sendLen = rawData.size(); } else if (fishCommand == FISH_RETR) { data(QByteArray()); } finished(); } } void fishProtocol::writeStdin(const QString &line) { qlist.append(E(line)); if (writeReady) { writeReady = false; //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n'))); myDebug( << "Writing: " << qlist.first()); myDebug( << "---------"); writeChild((const char *)qlist.first(), qlist.first().length()); } } void fishProtocol::sent() { if (rawWrite > 0) { myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite); writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite)); rawWrite -= rawData.size(); if (rawWrite > 0) { dataReq(); if (readData(rawData) <= 0) { shutdownConnection(); } } return; } else if (rawWrite == 0) { // workaround: some dd's insist in reading multiples of // 8 bytes, swallowing up to seven bytes. Sending // newlines is safe even when a sane dd is used writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15); rawWrite = -1; return; } if (qlist.count() > 0) qlist.erase(qlist.begin()); if (qlist.count() == 0) { writeReady = true; } else { //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n'))); myDebug( << "Writing: " << qlist.first()); myDebug( << "---------"); writeChild((const char *)qlist.first(),qlist.first().length()); } } int fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen) { int pos = 0; do { if (buflen <= 0) break; if (rawRead > 0) { myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead); int dataSize = (rawRead > buflen?buflen:rawRead); if (!mimeTypeSent) { int mimeSize = qMin(dataSize, (int)(mimeBuffer.size()-dataRead)); memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize); dataRead += mimeSize; rawRead -= mimeSize; buffer += mimeSize; buflen -= mimeSize; if (rawRead == 0) // End of data mimeBuffer.resize(dataRead); if (dataRead < (int)mimeBuffer.size()) { myDebug( << "wait for more"); break; } QMimeDatabase db; sendmimeType(db.mimeTypeForFileNameAndData(url.path(), mimeBuffer).name()); mimeTypeSent = true; if (fishCommand != FISH_READ) { totalSize(dataRead + rawRead); data(mimeBuffer); processedSize(dataRead); } mimeBuffer.resize(1024); pos = 0; continue; // Process rest of buffer/buflen } QByteArray bdata(buffer,dataSize); data(bdata); dataRead += dataSize; rawRead -= dataSize; processedSize(dataRead); if (rawRead <= 0) { buffer += dataSize; buflen -= dataSize; } else { return 0; } } if (buflen <= 0) break; pos = 0; // Find newline while((pos < buflen) && (buffer[pos] != '\n')) ++pos; if (pos < buflen) { QString s = remoteEncoding()->decode(QByteArray(buffer,pos)); buffer += pos+1; buflen -= pos+1; manageConnection(s); pos = 0; // Find next newline while((pos < buflen) && (buffer[pos] != '\n')) ++pos; } } while (childPid && buflen && (rawRead > 0 || pos < buflen)); return buflen; } /** get a file */ void fishProtocol::get(const QUrl& u){ myDebug( << "@@@@@@@@@ get " << u); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { recvLen = -1; sendCommand(FISH_RETR,E(url.path())); } run(); } /** put a file */ void fishProtocol::put(const QUrl& u, int permissions, KIO::JobFlags flags) { myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << (flags & KIO::Overwrite) << " " /* << resume */); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { putPerm = permissions; checkOverwrite = flags & KIO::Overwrite; checkExist = false; putPos = 0; listReason = CHECK; sendCommand(FISH_LIST,E(url.path())); sendCommand(FISH_STOR,"0",E(url.path())); const QString mtimeStr = metaData( "modified" ); if ( !mtimeStr.isEmpty() ) { QDateTime dt = QDateTime::fromString( mtimeStr, Qt::ISODate ); // TODO set modification time on url.path() somehow // see FileProtocol::put if using utime() to do that. } } run(); } /** executes next command in sequence or calls finished() if all is done */ void fishProtocol::finished() { if (commandList.count() > 0) { fishCommand = (fish_command_type)commandCodes.first(); errorCount = -fishInfo[fishCommand].lines; rawRead = 0; rawWrite = -1; udsEntry.clear(); udsStatEntry.clear(); writeStdin(commandList.first()); //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().indexOf("\n")-1))+"..."); commandList.erase(commandList.begin()); commandCodes.erase(commandCodes.begin()); } else { myDebug( << "_______ emitting finished()"); SlaveBase::finished(); isRunning = false; } } /** aborts command sequence and calls error() */ void fishProtocol::error(int type, const QString &detail) { commandList.clear(); commandCodes.clear(); myDebug( << "ERROR: " << type << " - " << detail); SlaveBase::error(type,detail); isRunning = false; } /** executes a chain of commands */ void fishProtocol::run() /* This function writes to childFd fish commands (like #STOR 0 /tmp/test ...) that are stored in outBuf and reads from childFd the remote host's response. ChildFd is the fd to a process that communicates with .fishsrv.pl typically running on another computer. */ { if (!isRunning) { int rc; isRunning = true; finished(); #ifndef Q_OS_WIN fd_set rfds, wfds; FD_ZERO(&rfds); #endif char buf[32768]; int offset = 0; while (isRunning) { #ifndef Q_OS_WIN FD_SET(childFd,&rfds); FD_ZERO(&wfds); if (outBufPos >= 0) FD_SET(childFd,&wfds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 1000; - rc = select(childFd+1, &rfds, &wfds, NULL, &timeout); + rc = select(childFd+1, &rfds, &wfds, nullptr, &timeout); if (rc < 0) { if (errno == EINTR) continue; myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno)); error(ERR_CONNECTION_BROKEN,connectionHost); shutdownConnection(); return; } // We first write the complete buffer, including all newlines. // Do: send command and newlines, expect response then // Do not: send commands, expect response, send newlines, expect response on newlines // Newlines do not trigger a response. if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) { if (outBufLen-outBufPos > 0) rc = ::write(childFd, outBuf + outBufPos, outBufLen - outBufPos); #else if (outBufPos >= 0) { if (outBufLen-outBufPos > 0) { rc = childPid->write(outBuf); } #endif else rc = 0; if (rc >= 0) outBufPos += rc; else { #ifndef Q_OS_WIN if (errno == EINTR) continue; myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno)); #else myDebug( << "write failed, rc: " << rc); #endif error(ERR_CONNECTION_BROKEN,connectionHost); shutdownConnection(); return; } if (outBufPos >= outBufLen) { outBufPos = -1; - outBuf = NULL; + outBuf = nullptr; sent(); } } #ifndef Q_OS_WIN else if (FD_ISSET(childFd,&rfds)) { rc = ::read(childFd, buf + offset, sizeof(buf) - offset); #else else if (childPid->waitForReadyRead(1000)) { rc = childPid->read(buf + offset, sizeof(buf) - offset); #endif //myDebug( << "read " << rc << " bytes"); if (rc > 0) { int noff = received(buf, rc + offset); if (noff > 0) memmove(buf, buf + offset + rc - noff, noff); //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset)); offset = noff; } else { #ifndef Q_OS_WIN if (errno == EINTR) continue; myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno)); #else myDebug( << "read failed, rc: " << rc ); #endif error(ERR_CONNECTION_BROKEN,connectionHost); shutdownConnection(); return; } } if (wasKilled()) return; } } } /** stat a file */ void fishProtocol::stat(const QUrl& u){ myDebug( << "@@@@@@@@@ stat " << u); setHostInternal(u); url = u; isStat = true; // FIXME: just a workaround for konq deficiencies openConnection(); isStat = false; // FIXME: just a workaround for konq deficiencies if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { sendCommand(FISH_STAT,E(url.adjusted(QUrl::StripTrailingSlash).path())); } run(); } /** find mimetype for a file */ void fishProtocol::mimetype(const QUrl& u){ myDebug( << "@@@@@@@@@ mimetype " << u); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { recvLen = 1024; sendCommand(FISH_READ,"0","1024",E(url.path())); } run(); } /** list a directory */ void fishProtocol::listDir(const QUrl& u){ myDebug( << "@@@@@@@@@ listDir " << u); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { listReason = LIST; sendCommand(FISH_LIST,E(url.path())); } run(); } /** create a directory */ void fishProtocol::mkdir(const QUrl& u, int permissions) { myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { sendCommand(FISH_MKD,E(url.path())); if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); } run(); } /** rename a file */ void fishProtocol::rename(const QUrl& s, const QUrl& d, KIO::JobFlags flags) { myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << (flags & KIO::Overwrite)); if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) { error(ERR_UNSUPPORTED_ACTION,s.toDisplayString()); return; } setHostInternal(s); url = d; openConnection(); if (!isLoggedIn) return; QUrl src = s; url = url.adjusted(QUrl::NormalizePathSegments); src = src.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { if (!(flags & KIO::Overwrite)) { listReason = CHECK; checkOverwrite = false; sendCommand(FISH_LIST,E(url.path())); } sendCommand(FISH_RENAME,E(src.path()),E(url.path())); } run(); } /** create a symlink */ void fishProtocol::symlink(const QString& target, const QUrl& u, KIO::JobFlags flags) { myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << (flags & KIO::Overwrite)); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { if (!(flags & KIO::Overwrite)) { listReason = CHECK; checkOverwrite = false; sendCommand(FISH_LIST,E(url.path())); } sendCommand(FISH_SYMLINK,E(target),E(url.path())); } run(); } /** change file permissions */ void fishProtocol::chmod(const QUrl& u, int permissions){ myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); } run(); } /** copies a file */ void fishProtocol::copy(const QUrl &s, const QUrl &d, int permissions, KIO::JobFlags flags) { myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << (flags & KIO::Overwrite)); if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) { error(ERR_UNSUPPORTED_ACTION,s.toDisplayString()); return; } //myDebug( << s << endl << d); setHostInternal(s); url = d; openConnection(); if (!isLoggedIn) return; QUrl src = s; url = url.adjusted(QUrl::NormalizePathSegments); src = src.adjusted(QUrl::NormalizePathSegments); if (src.path().isEmpty()) { sendCommand(FISH_PWD); } else { if (!(flags & KIO::Overwrite)) { listReason = CHECK; checkOverwrite = false; sendCommand(FISH_LIST,E(url.path())); } sendCommand(FISH_COPY,E(src.path()),E(url.path())); if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); } run(); } /** removes a file or directory */ void fishProtocol::del(const QUrl &u, bool isFile){ myDebug( << "@@@@@@@@@ del " << u << " " << isFile); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; url = url.adjusted(QUrl::NormalizePathSegments); if (url.path().isEmpty()) { sendCommand(FISH_PWD); } else { sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path())); } run(); } /** special like background execute */ void fishProtocol::special( const QByteArray &data ){ int tmp; QDataStream stream(data); stream >> tmp; switch (tmp) { case FISH_EXEC_CMD: // SSH EXEC { QUrl u; QString command; QString tempfile; stream >> u; stream >> command; myDebug( << "@@@@@@@@@ exec " << u << " " << command); setHostInternal(u); url = u; openConnection(); if (!isLoggedIn) return; sendCommand(FISH_EXEC,E(command),E(url.path())); run(); break; } default: // Some command we don't understand. error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp)); break; } } /** report status */ void fishProtocol::slave_status() { myDebug( << "@@@@@@@@@ slave_status"); if (childPid > 0) slaveStatus(connectionHost,isLoggedIn); else slaveStatus(QString(),false); } diff --git a/kfileaudiopreview/kfileaudiopreview.cpp b/kfileaudiopreview/kfileaudiopreview.cpp index 698f1546..d444fe98 100644 --- a/kfileaudiopreview/kfileaudiopreview.cpp +++ b/kfileaudiopreview/kfileaudiopreview.cpp @@ -1,150 +1,150 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Carsten Pfeiffer Copyright (C) 2006 Matthias Kretz 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, version 2. 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 "kfileaudiopreview.h" #include #include #include #include #define TRANSLATION_DOMAIN "kfileaudiopreview5" #include #include #include #include #include #include #include #include #include "mediacontrols.h" #include #include K_PLUGIN_FACTORY(KFileAudioPreviewFactory, registerPlugin();) #define ConfigGroup "Audio Preview Settings" /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// using namespace Phonon; class KFileAudioPreview::Private { public: Private() - : player(0) - , audioOutput(0) - , videoWidget(0) + : player(nullptr) + , audioOutput(nullptr) + , videoWidget(nullptr) { } MediaObject *player; AudioOutput *audioOutput; VideoWidget *videoWidget; MediaControls *controls; }; KFileAudioPreview::KFileAudioPreview(QWidget *parent, const QVariantList &) : KPreviewWidgetBase(parent) , d(new Private) { setSupportedMimeTypes(BackendCapabilities::availableMimeTypes()); d->audioOutput = new AudioOutput(Phonon::NoCategory, this); d->videoWidget = new VideoWidget(this); d->videoWidget->hide(); d->controls = new MediaControls(this); d->controls->setEnabled(false); d->controls->setAudioOutput(d->audioOutput); m_autoPlay = new QCheckBox(i18n("Play &automatically"), this); KConfigGroup config(KSharedConfig::openConfig(), ConfigGroup); m_autoPlay->setChecked(config.readEntry("Autoplay", false)); connect(m_autoPlay, &QCheckBox::toggled, this, &KFileAudioPreview::toggleAuto); QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(d->videoWidget); layout->addWidget(d->controls); layout->addWidget(m_autoPlay, 0, Qt::AlignHCenter); layout->addStretch(); } KFileAudioPreview::~KFileAudioPreview() { KConfigGroup config(KSharedConfig::openConfig(), ConfigGroup); config.writeEntry("Autoplay", m_autoPlay->isChecked()); delete d; } void KFileAudioPreview::stateChanged(Phonon::State newstate, Phonon::State oldstate) { if (oldstate == Phonon::LoadingState && newstate != Phonon::ErrorState) { d->controls->setEnabled(true); } } void KFileAudioPreview::showPreview(const QUrl &url) { d->controls->setEnabled(false); if (!d->player) { d->player = new MediaObject(this); Phonon::createPath(d->player, d->videoWidget); Phonon::createPath(d->player, d->audioOutput); connect(d->player, &MediaObject::stateChanged, this, &KFileAudioPreview::stateChanged); d->videoWidget->setVisible(d->player->hasVideo()); connect(d->player, SIGNAL(hasVideoChanged(bool)), d->videoWidget, SLOT(setVisible(bool))); d->controls->setMediaObject(d->player); } d->player->setCurrentSource(url); if (m_autoPlay->isChecked()) { d->player->play(); } } void KFileAudioPreview::clearPreview() { if (d->player) { delete d->player; - d->player = 0; + d->player = nullptr; d->controls->setEnabled(false); } } void KFileAudioPreview::toggleAuto(bool on) { if (!d->player) { return; } if (on && d->controls->isEnabled()) { d->player->play(); } else { d->player->stop(); } } #include "kfileaudiopreview.moc" diff --git a/kfileaudiopreview/kfileaudiopreview.h b/kfileaudiopreview/kfileaudiopreview.h index 68155b6b..34b182ef 100644 --- a/kfileaudiopreview/kfileaudiopreview.h +++ b/kfileaudiopreview/kfileaudiopreview.h @@ -1,58 +1,58 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Carsten Pfeiffer Copyright (C) 2006 Matthias Kretz 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, version 2. 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. */ #ifndef KFILEAUDIOPREVIEW_H #define KFILEAUDIOPREVIEW_H #include #include #include class QCheckBox; class QUrl; /** * Audio "preview" widget for the file dialog. */ class KFileAudioPreview : public KPreviewWidgetBase { Q_OBJECT public: - explicit KFileAudioPreview(QWidget *parent = 0, + explicit KFileAudioPreview(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); ~KFileAudioPreview(); public Q_SLOTS: virtual void showPreview(const QUrl &url); virtual void clearPreview(); private Q_SLOTS: void toggleAuto(bool on); void stateChanged(Phonon::State newState, Phonon::State oldState); private: QCheckBox *m_autoPlay; private: class Private; Private *d; }; #endif // KFILEAUDIOPREVIEW_H diff --git a/kfileaudiopreview/mediacontrols.cpp b/kfileaudiopreview/mediacontrols.cpp index d76982d9..ab60961d 100644 --- a/kfileaudiopreview/mediacontrols.cpp +++ b/kfileaudiopreview/mediacontrols.cpp @@ -1,138 +1,138 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Matthias Kretz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "mediacontrols.h" #include "mediacontrols_p.h" #include #include #include #include #include #include namespace Phonon { MediaControls::MediaControls(QWidget *parent) : QWidget(parent), d_ptr(new MediaControlsPrivate(this)) { setMaximumHeight(32); } MediaControls::~MediaControls() { delete d_ptr; } bool MediaControls::isSeekSliderVisible() const { Q_D(const MediaControls); return d->seekSlider.isVisible(); } bool MediaControls::isVolumeControlVisible() const { Q_D(const MediaControls); return d->volumeSlider.isVisible(); } void MediaControls::setMediaObject(MediaObject *media) { Q_D(MediaControls); if (d->media) { disconnect(d->media, SIGNAL(destroyed()), this, SLOT(_k_mediaDestroyed())); disconnect(d->media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(_k_stateChanged(Phonon::State,Phonon::State))); disconnect(&d->playButton, SIGNAL(clicked()), d->media, SLOT(play())); disconnect(&d->pauseButton, SIGNAL(clicked()), d->media, SLOT(pause())); } d->media = media; if (media) { connect(media, SIGNAL(destroyed()), SLOT(_k_mediaDestroyed())); connect(media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), SLOT(_k_stateChanged(Phonon::State,Phonon::State))); connect(&d->playButton, SIGNAL(clicked()), media, SLOT(play())); connect(&d->pauseButton, SIGNAL(clicked()), media, SLOT(pause())); } d->seekSlider.setMediaObject(media); } void MediaControls::setAudioOutput(AudioOutput *audioOutput) { Q_D(MediaControls); d->volumeSlider.setAudioOutput(audioOutput); d->updateVolumeSliderVisibility(); - d->volumeSlider.setVisible(audioOutput != 0); + d->volumeSlider.setVisible(audioOutput != nullptr); } void MediaControls::setSeekSliderVisible(bool vis) { Q_D(MediaControls); d->seekSlider.setVisible(vis); } void MediaControls::setVolumeControlVisible(bool vis) { Q_D(MediaControls); d->volumeSlider.setVisible(vis); } void MediaControls::resizeEvent(QResizeEvent *) { Q_D(MediaControls); d->updateVolumeSliderVisibility(); } void MediaControlsPrivate::updateVolumeSliderVisibility() { bool isWide = q_ptr->width() > playButton.sizeHint().width() + seekSlider.sizeHint().width() + volumeSlider.sizeHint().width(); - bool hasAudio = volumeSlider.audioOutput() != 0; + bool hasAudio = volumeSlider.audioOutput() != nullptr; volumeSlider.setVisible(isWide && hasAudio); } void MediaControlsPrivate::_k_stateChanged(State newstate, State) { switch (newstate) { case Phonon::LoadingState: case Phonon::PausedState: case Phonon::StoppedState: playButton.show(); pauseButton.hide(); break; case Phonon::BufferingState: case Phonon::PlayingState: playButton.hide(); pauseButton.show(); break; case Phonon::ErrorState: return; } } void MediaControlsPrivate::_k_mediaDestroyed() { - media = 0; + media = nullptr; } } // namespace Phonon #include "moc_mediacontrols.cpp" diff --git a/kfileaudiopreview/mediacontrols.h b/kfileaudiopreview/mediacontrols.h index 4954caa5..3b983658 100644 --- a/kfileaudiopreview/mediacontrols.h +++ b/kfileaudiopreview/mediacontrols.h @@ -1,101 +1,101 @@ /* This file is part of the KDE project Copyright (C) 2006 Matthias Kretz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef PHONON_UI_MEDIACONTROLS_H #define PHONON_UI_MEDIACONTROLS_H #include #include namespace Phonon { class MediaObject; class AudioOutput; class MediaControlsPrivate; /** * \short Simple widget showing buttons to control an MediaObject * object. * * This widget shows the standard player controls. There's at least the * play/pause and stop buttons. If the media is seekable it shows a seek-slider. * Optional controls include a volume control and a loop control button. * * \author Matthias Kretz */ class MediaControls : public QWidget { Q_OBJECT Q_DECLARE_PRIVATE(MediaControls) /** * This property holds whether the slider showing the progress of the * playback is visible. * * By default the slider is visible. It is enabled/disabled automatically * depending on whether the media can be seeked or not. */ Q_PROPERTY(bool seekSliderVisible READ isSeekSliderVisible WRITE setSeekSliderVisible) /** * This property holds whether the slider controlling the volume is visible. * * By default the slider is visible if an AudioOutput has been set with * setAudioOutput. * * \see setAudioOutput */ Q_PROPERTY(bool volumeControlVisible READ isVolumeControlVisible WRITE setVolumeControlVisible) public: /** * Constructs a media control widget with a \p parent. */ - explicit MediaControls(QWidget *parent = 0); + explicit MediaControls(QWidget *parent = nullptr); ~MediaControls(); bool isSeekSliderVisible() const; bool isVolumeControlVisible() const; public Q_SLOTS: void setSeekSliderVisible(bool isVisible); void setVolumeControlVisible(bool isVisible); /** * Sets the media object to be controlled by this widget. */ void setMediaObject(MediaObject *mediaObject); /** * Sets the audio output object to be controlled by this widget. */ void setAudioOutput(AudioOutput *audioOutput); protected: void resizeEvent(QResizeEvent *event); private: Q_PRIVATE_SLOT(d_func(), void _k_stateChanged(Phonon::State, Phonon::State)) Q_PRIVATE_SLOT(d_func(), void _k_mediaDestroyed()) MediaControlsPrivate *const d_ptr; }; } // namespace Phonon #endif // PHONON_UI_MEDIACONTROLS_H diff --git a/kfileaudiopreview/mediacontrols_p.h b/kfileaudiopreview/mediacontrols_p.h index e8c34353..2672bc24 100644 --- a/kfileaudiopreview/mediacontrols_p.h +++ b/kfileaudiopreview/mediacontrols_p.h @@ -1,89 +1,89 @@ /* This file is part of the KDE project Copyright (C) 2007 Matthias Kretz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef PHONON_MEDIACONTROLS_P_H #define PHONON_MEDIACONTROLS_P_H #include "mediacontrols.h" #define TRANSLATION_DOMAIN "kfileaudiopreview5" #include #include #include #include #include #include namespace Phonon { class MediaControlsPrivate { Q_DECLARE_PUBLIC(MediaControls) protected: MediaControlsPrivate(MediaControls *parent) : q_ptr(parent), layout(parent), playButton(parent), pauseButton(parent), seekSlider(parent), volumeSlider(parent), - media(0) + media(nullptr) { int size = parent->style()->pixelMetric(QStyle::PM_ToolBarIconSize); QSize iconSize(size, size); playButton.setIconSize(iconSize); playButton.setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); playButton.setToolTip(i18n("start playback")); playButton.setAutoRaise(true); pauseButton.setIconSize(iconSize); pauseButton.setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); pauseButton.setToolTip(i18n("pause playback")); pauseButton.hide(); pauseButton.setAutoRaise(true); seekSlider.setIconVisible(false); volumeSlider.setOrientation(Qt::Horizontal); volumeSlider.setMaximumWidth(80); volumeSlider.hide(); layout.setMargin(0); layout.setSpacing(0); layout.addWidget(&playButton); layout.addWidget(&pauseButton); layout.addWidget(&seekSlider, 1); layout.addWidget(&volumeSlider); } MediaControls *q_ptr; QHBoxLayout layout; QToolButton playButton; QToolButton pauseButton; SeekSlider seekSlider; VolumeSlider volumeSlider; MediaObject *media; private: void _k_stateChanged(Phonon::State, Phonon::State); void _k_mediaDestroyed(); void updateVolumeSliderVisibility(); }; } // namespace Phonon #endif // PHONON_MEDIACONTROLS_P_H diff --git a/man/kio_man.cpp b/man/kio_man.cpp index d263456d..b0460035 100644 --- a/man/kio_man.cpp +++ b/man/kio_man.cpp @@ -1,1426 +1,1426 @@ /* This file is part of the KDE libraries Copyright (c) 2000 Matthias Hoelzer-Kluepfel 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 #include "man2html.h" #include #include using namespace KIO; -MANProtocol *MANProtocol::_self = 0; +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 = 0; + _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(0); // flush + 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; /* Determine type of man page file by checking its path. Determination by * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7: * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG * If the path name constains 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('/')); 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); mandir.setNameFilters(QStringList() << (filename.mid(filename.lastIndexOf('/') + 1) + ".*")); filename = lastdir + '/' + QFile::encodeName(mandir.entryList().first()); qCDebug(KIO_MAN_LOG) << "resolved to " << filename; } QIODevice *fd = KFilterDev::deviceForFile(filename); if ( !fd || !fd->open(QIODevice::ReadOnly)) { delete fd; - return 0; + return nullptr; } array = fd->readAll(); qCDebug(KIO_MAN_LOG) << "read " << array.size(); fd->close(); delete fd; } if (array.isEmpty()) - return 0; + return nullptr; // as we do not know in which encoding the man source is, try to automatically // detect it and always return it as UTF-8 KEncodingProber encodingProber; encodingProber.feed(array); qCDebug(KIO_MAN_LOG) << "auto-detect encoding for" << filename << "guess=" << encodingProber.encoding() << "confidence=" << encodingProber.confidence(); QString out = QTextCodec::codecForName(encodingProber.encoding())->toUnicode(array); array = out.toUtf8(); const int len = array.size(); char *buf = new char[len + 4]; memmove(buf + 1, array.data(), len); buf[0] = buf[len+1] = '\n'; // Start and end with an end of line buf[len+2] = buf[len+3] = '\0'; // Two NUL characters at end return buf; } //--------------------------------------------------------------------- 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; if ( !m_manCSSFile.isEmpty() ) os << "" << endl; os << "" << endl; os << "" << i18n("

KDE Man Viewer Error

") << errmsg << "" << endl; os << "" << endl; 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; if ( !m_manCSSFile.isEmpty() ) os << "" << endl; 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"<" << endl; os << "" << endl; os << "" << i18n("UNIX Manual Index") << "" << endl; if (!m_manCSSFile.isEmpty()) os << "" << endl; os << "" << endl; os << "

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

" << endl; // ### 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; 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; } os << "
" << i18n("Section %1", *it) << "  " << sectionName(*it) << "
" << endl; // print footer os << "" << endl; 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", - NULL }; + 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 } // Constucted 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; if ( !m_manCSSFile.isEmpty() ) os_h << "" << endl; os_h << "" << endl << "" << endl; 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; // 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; infoMessage(QString()); data(array_h + array_d); finished(); return; } QMap indexmap = buildIndexMap(section); // print out the list os << "" << endl; 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 = 0; + manpage_end = nullptr; else manpage_end = end; - if (NULL == manpage_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 = {0l,0l,0}; + 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; 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; const QString appendixstr = QString( " [%3]\n" ).arg(firstchar).arg(firstchar).arg(firstchar); indexLine.append(appendixstr); } os << "" << endl; 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 << indexLine << endl; // print footer os << "" << endl; // set the links "toolbar" also at the top os_h << indexLine << endl; 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.insert( KIO::UDSEntry::UDS_NAME, sectionName( *it ) ); uds_entry.insert( KIO::UDSEntry::UDS_URL, name ); uds_entry.insert( 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.insert( KIO::UDSEntry::UDS_NAME, *it ); uds_entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); uds_entry.insert(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/man/kmanpart.cpp b/man/kmanpart.cpp index 78061562..a79099fb 100644 --- a/man/kmanpart.cpp +++ b/man/kmanpart.cpp @@ -1,83 +1,83 @@ /* This file is part of the KDE project Copyright (C) 2002 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 "kmanpart.h" #include #include #include #include static KAboutData createAboutData() { return KAboutData("kmanpart", i18n("KMan"), PROJECT_VERSION); } K_PLUGIN_FACTORY(KManPartFactory, registerPlugin();) KManPart::KManPart(QWidget * parentWidget, QObject* parent, const QVariantList&) : KHTMLPart(parentWidget, parent) -,m_job(0) +,m_job(nullptr) { setComponentData(createAboutData()); m_extension = new KParts::BrowserExtension(this); } bool KManPart::openUrl( const QUrl &url ) { // KHTML would detect text/plain, but we are going to write HTML to it. KParts::OpenUrlArguments args(arguments()); args.setMimeType("text/html"); setArguments(args); return KParts::ReadOnlyPart::openUrl(url); } bool KManPart::openFile() { - if (m_job!=0) + if (m_job!=nullptr) m_job->kill(); begin(); QUrl url; url.setScheme( "man" ); url.setPath( localFilePath() ); m_job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo ); connect(m_job, &KIO::TransferJob::data, this, &KManPart::readData); connect(m_job, &KIO::TransferJob::result, this, &KManPart::jobDone); return true; } void KManPart::readData(KIO::Job * , const QByteArray & data) { write(data,data.size()); } void KManPart::jobDone( KJob *) { - m_job=0; + m_job=nullptr; end(); } #include "kmanpart.moc" diff --git a/man/man2html.cpp b/man/man2html.cpp index 4c936cdd..0cb14487 100644 --- a/man/man2html.cpp +++ b/man/man2html.cpp @@ -1,6138 +1,6138 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow Copyright (C) 2005 Nicolas GOUTTE Copyright (C) 2011 Martin Koller ... and others (see SVN history) */ // Start of verbatim comment /* ** This program was written by Richard Verhoeven (NL:5482ZX35) ** at the Eindhoven University of Technology. Email: rcb5@win.tue.nl ** ** Permission is granted to distribute, modify and use this program as long ** as this comment is not removed or changed. */ // End of verbatim comment /* * man2html-linux-1.0/1.1 * This version modified for Redhat/Caldera linux - March 1996. * Michael Hamilton . * * man2html-linux-1.2 * Added support for BSD mandoc pages - I didn't have any documentation * on the mandoc macros, so I may have missed some. * Michael Hamilton . * * vh-man2html-1.3 * Renamed to avoid confusion (V for Verhoeven, H for Hamilton). * * vh-man2html-1.4 * Now uses /etc/man.config * Added support for compressed pages. * Added "length-safe" string operations for client input parameters. * More secure, -M secured, and client input string lengths checked. * */ /* ** If you want to use this program for your WWW server, adjust the line ** which defines the CGIBASE or compile it with the -DCGIBASE='"..."' option. ** ** You have to adjust the built-in manpath to your local system. Note that ** every directory should start and end with the '/' and that the first ** directory should be "/" to allow a full path as an argument. ** ** The program first check if PATH_INFO contains some information. ** If it does (t.i. man2html/some/thing is used), the program will look ** for a manpage called PATH_INFO in the manpath. ** ** Otherwise the manpath is searched for the specified command line argument, ** where the following options can be used: ** ** name name of manpage (csh, printf, xv, troff) ** section the section (1 2 3 4 5 6 7 8 9 n l 1v ...) ** -M path an extra directory to look for manpages (replaces "/") ** ** If man2html finds multiple manpages that satisfy the options, an index ** is displayed and the user can make a choice. If only one page is ** found, that page will be displayed. ** ** man2html will add links to the converted manpages. The function add_links ** is used for that. At the moment it will add links as follows, where ** indicates what should match to start with: ** ^^^ ** Recognition Item Link ** ---------------------------------------------------------- ** name(*) Manpage ../man?/name.* ** ^ ** name@hostname Email address mailto:name@hostname ** ^ ** method://string URL method://string ** ^^^ ** www.host.name WWW server http://www.host.name ** ^^^^ ** ftp.host.name FTP server ftp://ftp.host.name ** ^^^^ ** Include file file:/usr/include/file.h ** ^^^ ** ** Since man2html does not check if manpages, hosts or email addresses exist, ** some links might not work. For manpages, some extra checks are performed ** to make sure not every () pair creates a link. Also out of date pages ** might point to incorrect places. ** ** The program will not allow users to get system specific files, such as ** /etc/passwd. It will check that "man" is part of the specified file and ** that "/../" isn't. Even if someone manages to get such file, man2html will ** handle it like a manpage and will usually not produce any output (or crash). ** ** If you find any bugs when normal manpages are converted, please report ** them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle ** the manpage correct. ** ** Known bugs and missing features: ** ** * Equations are not converted at all. ** * Tables are converted but some features are not possible in html. ** * The tabbing environment is converted by counting characters and adding ** spaces. This might go wrong (outside
)
 **  * Some manpages rely on the fact that troff/nroff is used to convert
 **    them and use features which are not descripted in the man manpages.
 **    (definitions, calculations, conditionals, requests). I can't guarantee
 **    that all these features work on all manpages. (I didn't have the
 **    time to look through all the available manpages.)
 */
 
 #include "man2html.h"
 #include "kio_man_debug.h"
 #include "request_hash.h"
 
 #include 
 
 #include 
 
 #include 
 #include 
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #ifdef SIMPLE_MAN2HTML
 # include 
 # include 
 # include 
 # include 
 # include 
 # define kDebug(x) QDebug(QtDebugMsg)
 # define kWarning(x) QDebug(QtWarningMsg) << "WARNING "
 # define BYTEARRAY(x) x.constData()
 #else
 # include 
 # include 
 # include 
 # include 
 # define BYTEARRAY(x) x
 #endif
 
 #define NULL_TERMINATED(n) ((n) + 1)
 
 #define HUGE_STR_MAX  10000
 #define LARGE_STR_MAX 2000
 #define MED_STR_MAX   500
 #define SMALL_STR_MAX 100
 #define TINY_STR_MAX  10
 
 #define DOCTYPE "\n"
 
 /* mdoc(7) Bl/El lists to HTML list types */
 #define BL_DESC_LIST   1
 #define BL_BULLET_LIST 2
 #define BL_ENUM_LIST   4
 
 /* mdoc(7) Bd/Ed example(?) blocks */
 #define BD_LITERAL  1
 #define BD_INDENT   2
 
 static int s_nroff = 1; // NROFF mode by default
 
 static QByteArray mandoc_name;  // Nm can store the first used name
 
 static int mandoc_name_count = 0; /* Don't break on the first Nm */
 
 static char *strlimitcpy(char *to, char *from, int n, int limit)
 {                               /* Assumes space for limit plus a null */
   const int len = n > limit ? limit : n;
   qstrncpy(to, from, len + 1);
   to[len] = '\0';
   return to;
 }
 
 /* below this you should not change anything unless you know a lot
 ** about this program or about troff.
 */
 
 
 /// Structure for character definitions
 struct CSTRDEF
 {
   int nr, slen;
   const char *st;
 };
 
 
 
 const char NEWLINE[2] = "\n";
 
 /**
  * Class for defining strings and macros
  */
 class StringDefinition
 {
   public:
     StringDefinition(void) : m_length(0) {}
     StringDefinition(int len, const char* cstr) : m_length(len), m_output(cstr) {}
   public:
     int m_length; ///< Length of output text
     QByteArray m_output; ///< Defined string
 };
 
 /**
  * Class for defining number registers
  * \note Not for internal read-only registers
  */
 class NumberDefinition
 {
   public:
     NumberDefinition(void) : m_value(0), m_increment(0) {}
     NumberDefinition(int value) : m_value(value), m_increment(0) {}
     NumberDefinition(int value, int incr) : m_value(value), m_increment(incr) {}
   public:
     int m_value; ///< value of number register
     int m_increment; ///< Increment of number register
     // ### TODO: display form (.af)
 };
 
 /**
  * Map of character definitions
  */
 static QMap s_characterDefinitionMap;
 
 /**
  * Map of string variable and macro definitions
  * \note String variables and macros are the same thing!
  */
 static QMap s_stringDefinitionMap;
 
 /**
  * Map of number registers
  * \note Intern number registers (starting with a dot are not handled here)
  */
 static QMap s_numberDefinitionMap;
 
 static void fill_old_character_definitions(void);
 
 /**
  * Initialize character variables
  */
 static void InitCharacterDefinitions(void)
 {
   fill_old_character_definitions();
   // ### HACK: as we are converting to HTML too early, define characters with HTML references
   s_characterDefinitionMap.insert("<-", StringDefinition(1, "←"));     // <-
   s_characterDefinitionMap.insert("->", StringDefinition(1, "→"));     // ->
   s_characterDefinitionMap.insert("<>", StringDefinition(1, "↔"));     // <>
   s_characterDefinitionMap.insert("<=", StringDefinition(1, "≤"));     // <=
   s_characterDefinitionMap.insert(">=", StringDefinition(1, "≥"));     // >=
   // End HACK
 }
 
 /**
  * Initialize string variables
  */
 static void InitStringDefinitions(void)
 {
   // mdoc-only, see mdoc.samples(7)
   s_stringDefinitionMap.insert("<=", StringDefinition(1, "≤"));
   s_stringDefinitionMap.insert(">=", StringDefinition(1, "≥"));
   s_stringDefinitionMap.insert("Rq", StringDefinition(1, "”"));
   s_stringDefinitionMap.insert("Lq", StringDefinition(1, "“"));
   s_stringDefinitionMap.insert("ua", StringDefinition(1, "&circ"));     // Note this is different from \(ua
   s_stringDefinitionMap.insert("aa", StringDefinition(1, "´"));
   s_stringDefinitionMap.insert("ga", StringDefinition(1, "`"));
   s_stringDefinitionMap.insert("q", StringDefinition(1, """));
   s_stringDefinitionMap.insert("Pi", StringDefinition(1, "π"));
   s_stringDefinitionMap.insert("Ne", StringDefinition(1, "≠"));
   s_stringDefinitionMap.insert("Le", StringDefinition(1, "≤"));
   s_stringDefinitionMap.insert("Ge", StringDefinition(1, "≥"));
   s_stringDefinitionMap.insert("Lt", StringDefinition(1, "<"));
   s_stringDefinitionMap.insert("Gt", StringDefinition(1, ">"));
   s_stringDefinitionMap.insert("Pm", StringDefinition(1, "±"));
   s_stringDefinitionMap.insert("If", StringDefinition(1, "∞"));
   s_stringDefinitionMap.insert("Na", StringDefinition(3, "NaN"));
   s_stringDefinitionMap.insert("Ba", StringDefinition(1, "|"));
   // end mdoc-only
   // man(7)
   s_stringDefinitionMap.insert("Tm", StringDefinition(1, "™"));     // \*(TM
   s_stringDefinitionMap.insert("R", StringDefinition(1, "®"));     // \*R
   s_stringDefinitionMap.insert("lq", StringDefinition(1, "“"));     // Left angled double quote
   s_stringDefinitionMap.insert("rq", StringDefinition(1, "”"));     // Right angled double quote
   // end man(7)
   // Missing characters from man(7):
   // \*S "Change to default font size"
 #ifndef SIMPLE_MAN2HTML
   // Special KDE KIO man:
   const QByteArray kdeversion(KDE_VERSION_STRING);
   s_stringDefinitionMap.insert(".KDE_VERSION_STRING", StringDefinition(kdeversion.length(), kdeversion));
 #endif
 }
 
 /**
  * Initialize number registers
  * \note Internal read-only registers are not handled here
  */
 static void InitNumberDefinitions(void)
 {
   // As the date number registers are more for end-users, better choose local time.
   // Groff seems to support Gregorian dates only
   QDate today(QDate::currentDate());
   s_numberDefinitionMap.insert("year", today.year());   // Y2K-correct year
   s_numberDefinitionMap.insert("yr", today.year() - 1900);   // Y2K-incorrect year
   s_numberDefinitionMap.insert("mo", today.month());
   s_numberDefinitionMap.insert("dy", today.day());
   s_numberDefinitionMap.insert("dw", today.dayOfWeek());
 }
 
 
 #define V(A,B) ((A)*256+(B))
 
 //used in expand_char, e.g. for "\(bu"
 // see groff_char(7) for list
 static const CSTRDEF standardchar[] =
 {
   { V('*', '*'), 1, "*" },
   { V('*', 'A'), 1, "Α" },
   { V('*', 'B'), 1, "Β" },
   { V('*', 'C'), 1, "Ξ" },
   { V('*', 'D'), 1, "Δ" },
   { V('*', 'E'), 1, "Ε" },
   { V('*', 'F'), 1, "Φ" },
   { V('*', 'G'), 1, "Γ" },
   { V('*', 'H'), 1, "Θ" },
   { V('*', 'I'), 1, "Ι" },
   { V('*', 'K'), 1, "Κ" },
   { V('*', 'L'), 1, "Λ" },
   { V('*', 'M'), 1, "&Mu:" },
   { V('*', 'N'), 1, "Ν" },
   { V('*', 'O'), 1, "Ο" },
   { V('*', 'P'), 1, "Π" },
   { V('*', 'Q'), 1, "Ψ" },
   { V('*', 'R'), 1, "Ρ" },
   { V('*', 'S'), 1, "Σ" },
   { V('*', 'T'), 1, "Τ" },
   { V('*', 'U'), 1, "Υ" },
   { V('*', 'W'), 1, "Ω" },
   { V('*', 'X'), 1, "Χ" },
   { V('*', 'Y'), 1, "Η" },
   { V('*', 'Z'), 1, "Ζ" },
   { V('*', 'a'), 1, "α"},
   { V('*', 'b'), 1, "β"},
   { V('*', 'c'), 1, "ξ"},
   { V('*', 'd'), 1, "δ"},
   { V('*', 'e'), 1, "ε"},
   { V('*', 'f'), 1, "φ"},
   { V('*', 'g'), 1, "γ"},
   { V('*', 'h'), 1, "θ"},
   { V('*', 'i'), 1, "ι"},
   { V('*', 'k'), 1, "κ"},
   { V('*', 'l'), 1, "λ"},
   { V('*', 'm'), 1, "μ" },
   { V('*', 'n'), 1, "ν"},
   { V('*', 'o'), 1, "ο"},
   { V('*', 'p'), 1, "π"},
   { V('*', 'q'), 1, "ψ"},
   { V('*', 'r'), 1, "ρ"},
   { V('*', 's'), 1, "σ"},
   { V('*', 't'), 1, "τ"},
   { V('*', 'u'), 1, "υ"},
   { V('*', 'w'), 1, "ω"},
   { V('*', 'x'), 1, "χ"},
   { V('*', 'y'), 1, "η"},
   { V('*', 'z'), 1, "ζ"},
   { V('+', '-'), 1, "±" }, // not in groff_char(7)
   { V('+', 'f'), 1, "φ"}, // phi1, we use the standard phi
   { V('+', 'h'), 1, "θ"}, // theta1, we use the standard theta
   { V('+', 'p'), 1, "ω"}, // omega1, we use the standard omega
   { V('1', '2'), 1, "½" },
   { V('1', '4'), 1, "¼" },
   { V('3', '4'), 1, "¾" },
   { V('F', 'i'), 1, "ffi" }, // ffi ligature
   { V('F', 'l'), 1, "ffl" }, // ffl ligature
   { V('a', 'p'), 1, "~" },
   { V('b', 'r'), 1, "|" },
   { V('b', 'u'), 1, "•" },
   { V('b', 'v'), 1, "|" },
   { V('c', 'i'), 1, "○" }, // circle ### TODO verify
   { V('c', 'o'), 1, "©" },
   { V('c', 't'), 1, "¢" },
   { V('d', 'e'), 1, "°" },
   { V('d', 'g'), 1, "†" },
   { V('d', 'i'), 1, "÷" },
   { V('e', 'm'), 1, "—" },
   { V('e', 'n'), 1, "–"},
   { V('e', 'q'), 1, "=" },
   { V('e', 's'), 1, "∅" },
   { V('f', 'f'), 1, "�xFB00;" }, // ff ligature
   { V('f', 'i'), 1, "�xFB01;" }, // fi ligature
   { V('f', 'l'), 1, "�xFB02;" }, // fl ligature
   { V('f', 'm'), 1, "′" },
   { V('g', 'a'), 1, "`" },
   { V('h', 'y'), 1, "-" },
   { V('l', 'c'), 2, "|¯" }, // ### TODO: not in groff_char(7)
   { V('l', 'f'), 2, "|_" }, // ### TODO: not in groff_char(7)
   { V('l', 'k'), 1, "{" }, // ### TODO: not in groff_char(7)
   { V('m', 'i'), 1, "-" }, // ### TODO: not in groff_char(7)
   { V('m', 'u'), 1, "×" },
   { V('n', 'o'), 1, "¬" },
   { V('o', 'r'), 1, "|" },
   { V('p', 'l'), 1, "+" },
   { V('r', 'c'), 2, "¯|" }, // ### TODO: not in groff_char(7)
   { V('r', 'f'), 2, "_|" }, // ### TODO: not in groff_char(7)
   { V('r', 'g'), 1, "®" },
   { V('r', 'k'), 1, "}" }, // ### TODO: not in groff_char(7)
   { V('r', 'n'), 1, "‾" },
   { V('r', 'u'), 1, "_" },
   { V('s', 'c'), 1, "§" },
   { V('s', 'l'), 1, "/" },
   { V('s', 'q'), 2, "□" }, // WHITE SQUARE
   { V('t', 's'), 1, "ς" }, // FINAL SIGMA
   { V('u', 'l'), 1, "_" },
   { V('-', 'D'), 1, "Ð" },
   { V('S', 'd'), 1, "ð" },
   { V('T', 'P'), 1, "Þ" },
   { V('T', 'p'), 1, "þ" },
   { V('A', 'E'), 1, "Æ" },
   { V('a', 'e'), 1, "æ" },
   { V('O', 'E'), 1, "Œ" },
   { V('o', 'e'), 1, "œ" },
   { V('s', 's'), 1, "ß" },
   { V('\'', 'A'), 1, "Á" },
   { V('\'', 'E'), 1, "É" },
   { V('\'', 'I'), 1, "Í" },
   { V('\'', 'O'), 1, "Ó" },
   { V('\'', 'U'), 1, "Ú" },
   { V('\'', 'Y'), 1, "Ý" },
   { V('\'', 'a'), 1, "á" },
   { V('\'', 'e'), 1, "é" },
   { V('\'', 'i'), 1, "í" },
   { V('\'', 'o'), 1, "ó" },
   { V('\'', 'u'), 1, "ú" },
   { V('\'', 'y'), 1, "ý" },
   { V(':', 'A'), 1, "Ä" },
   { V(':', 'E'), 1, "Ë" },
   { V(':', 'I'), 1, "Ï" },
   { V(':', 'O'), 1, "Ö" },
   { V(':', 'U'), 1, "Ü" },
   { V(':', 'a'), 1, "ä" },
   { V(':', 'e'), 1, "ë" },
   { V(':', 'i'), 1, "ï" },
   { V(':', 'o'), 1, "ö" },
   { V(':', 'u'), 1, "ü" },
   { V(':', 'y'), 1, "ÿ" },
   { V('^', 'A'), 1, "Â" },
   { V('^', 'E'), 1, "Ê" },
   { V('^', 'I'), 1, "Î" },
   { V('^', 'O'), 1, "Ô" },
   { V('^', 'U'), 1, "Û" },
   { V('^', 'a'), 1, "â" },
   { V('^', 'e'), 1, "ê" },
   { V('^', 'i'), 1, "î" },
   { V('^', 'o'), 1, "ô" },
   { V('^', 'u'), 1, "û" },
   { V('`', 'A'), 1, "À" },
   { V('`', 'E'), 1, "È" },
   { V('`', 'I'), 1, "Ì" },
   { V('`', 'O'), 1, "Ò" },
   { V('`', 'U'), 1, "Ù" },
   { V('`', 'a'), 1, "à" },
   { V('`', 'e'), 1, "è" },
   { V('`', 'i'), 1, "ì" },
   { V('`', 'o'), 1, "ò" },
   { V('`', 'u'), 1, "ù" },
   { V('~', 'A'), 1, "Ã" },
   { V('~', 'N'), 1, "Ñ" },
   { V('~', 'O'), 1, "Õ" },
   { V('~', 'a'), 1, "ã" },
   { V('~', 'n'), 1, "&ntidle;" },
   { V('~', 'o'), 1, "&otidle;" },
   { V(',', 'C'), 1, "Ç" },
   { V(',', 'c'), 1, "ç" },
   { V('/', 'L'), 1, "Ł" },
   { V('/', 'l'), 1, "ł" },
   { V('/', 'O'), 1, "Ø" },
   { V('/', 'o'), 1, "ø" },
   { V('o', 'A'), 1, "Å" },
   { V('o', 'a'), 1, "å" },
   { V('a', '"'), 1, "\"" },
   { V('a', '-'), 1, "¯" },
   { V('a', '.'), 1, "." },
   { V('a', '^'), 1, "ˆ" },
   { V('a', 'a'), 1, "´" },
   { V('a', 'b'), 1, "`" },
   { V('a', 'c'), 1, "¸" },
   { V('a', 'd'), 1, "¨" },
   { V('a', 'h'), 1, "˂" }, // caron
   { V('a', 'o'), 1, "˚" }, // ring
   { V('a', '~'), 1, "˜" },
   { V('h', 'o'), 1, "˛" }, // ogonek
   { V('.', 'i'), 1, "ı" }, // dot less i
   { V('C', 's'), 1, "¤" }, //krazy:exclude=spelling
   { V('D', 'o'), 1, "$" },
   { V('P', 'o'), 1, "£" },
   { V('Y', 'e'), 1, "¥" },
   { V('F', 'n'), 1, "ƒ" },
   { V('F', 'o'), 1, "«" },
   { V('F', 'c'), 1, "»" },
   { V('f', 'o'), 1, "‹" }, // single left guillemet
   { V('f', 'c'), 1, "›" }, // single right guillemet
   { V('r', '!'), 1, "&iecl;" },
   { V('r', '?'), 1, "¿" },
   { V('O', 'f'), 1, "ª" },
   { V('O', 'm'), 1, "º" },
   { V('p', 'c'), 1, "·" },
   { V('S', '1'), 1, "¹" },
   { V('S', '2'), 1, "²" },
   { V('S', '3'), 1, "³" },
   { V('<', '-'), 1, "←" },
   { V('-', '>'), 1, "→" },
   { V('<', '>'), 1, "↔" },
   { V('d', 'a'), 1, "↓" },
   { V('u', 'a'), 1, "↑" },
   { V('l', 'A'), 1, "⇐" },
   { V('r', 'A'), 1, "⇒" },
   { V('h', 'A'), 1, "⇔" },
   { V('d', 'A'), 1, "⇓" },
   { V('u', 'A'), 1, "⇑" },
   { V('b', 'a'), 1, "|" },
   { V('b', 'b'), 1, "¦" },
   { V('t', 'm'), 1, "™" },
   { V('d', 'd'), 1, "‡" },
   { V('p', 's'), 1, "¶" },
   { V('%', '0'), 1, "‰" },
   { V('f', '/'), 1, "⁄" }, // Fraction slash
   { V('s', 'd'), 1, "″" },
   { V('h', 'a'), 1, "^" },
   { V('t', 'i'), 1, "&tidle;" },
   { V('l', 'B'), 1, "[" },
   { V('r', 'B'), 1, "]" },
   { V('l', 'C'), 1, "{" },
   { V('r', 'C'), 1, "}" },
   { V('l', 'a'), 1, "<" },
   { V('r', 'a'), 1, ">" },
   { V('l', 'h'), 1, "≤" },
   { V('r', 'h'), 1, "≥" },
   { V('B', 'q'), 1, "„" },
   { V('b', 'q'), 1, "‚" },
   { V('l', 'q'), 1, "“" },
   { V('r', 'q'), 1, "”" },
   { V('o', 'q'), 1, "‘" },
   { V('c', 'q'), 1, "’" },
   { V('a', 'q'), 1, "'" },
   { V('d', 'q'), 1, "\"" },
   { V('a', 't'), 1, "@" },
   { V('s', 'h'), 1, "#" },
   { V('r', 's'), 1, "\\" },
   { V('t', 'f'), 1, "∴" },
   { V('~', '~'), 1, "≅" },
   { V('~', '='), 1, "≈" },
   { V('!', '='), 1, "≠" },
   { V('<', '='), 1, "≤" },
   { V('=', '='), 1, "≡" },
   { V('=', '~'), 1, "≅" }, // ### TODO: verify
   { V('>', '='), 1, "≥" },
   { V('A', 'N'), 1, "∧" },
   { V('O', 'R'), 1, "∨" },
   { V('t', 'e'), 1, "∃" },
   { V('f', 'a'), 1, "∀" },
   { V('A', 'h'), 1, "ℵ" },
   { V('I', 'm'), 1, "ℑ" },
   { V('R', 'e'), 1, "ℜ" },
   { V('i', 'f'), 1, "∞" },
   { V('m', 'd'), 1, "⋅" },
   { V('m', 'o'), 1, "∆" }, // element ### TODO verify
   { V('n', 'm'), 1, "∉" },
   { V('p', 't'), 1, "∝" },
   { V('p', 'p'), 1, "⊥" },
   { V('s', 'b'), 1, "⊂" },
   { V('s', 'p'), 1, "⊃" },
   { V('i', 'b'), 1, "⊆" },
   { V('i', 'p'), 1, "⊇" },
   { V('i', 's'), 1, "∫" },
   { V('s', 'r'), 1, "√" },
   { V('p', 'd'), 1, "∂" },
   { V('c', '*'), 1, "⊗" },
   { V('c', '+'), 1, "⊕" },
   { V('c', 'a'), 1, "∩" },
   { V('c', 'u'), 1, "∪" },
   { V('g', 'r'), 1, "V" }, // gradient ### TODO Where in Unicode?
   { V('C', 'R'), 1, "↵" },
   { V('s', 't'), 2, "-)" }, // "such that" ### TODO Where in Unicode?
   { V('/', '_'), 1, "∠" },
   { V('w', 'p'), 1, "℘" },
   { V('l', 'z'), 1, "◊" },
   { V('a', 'n'), 1, "-" }, // "horizontal arrow extension"  ### TODO Where in Unicode?
 };
 
 /* default: print code */
 
 
 /* static char eqndelimopen=0, eqndelimclose=0; */
 static char escapesym = '\\', nobreaksym = '\'', controlsym = '.', fieldsym = 0, padsym = 0;
 
-static char *buffer = NULL;
+static char *buffer = nullptr;
 static int buffpos = 0, buffmax = 0;
 static bool scaninbuff = false;
 static int itemdepth = 0;
 static int section = 0;
 static int dl_set[20] = { 0 };
 static QStack listItemStack;
 static bool still_dd = 0;
 static int tabstops[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 };
 static int maxtstop = 12;
 static int curpos = 0;
 static bool break_the_while_loop = false;
 
 static char *scan_troff(char *c, bool san, char **result);
 static char *scan_troff_mandoc(char *c, bool san, char **result);
 static int getNumberRegisterValue(const QByteArray &name, int sign = 0);
 
 static QList s_argumentList;
 
 static QByteArray cssFile;
 
 static QByteArray s_dollarZero; // Value of $0
 
 void setCssFile(const QByteArray& _cssFile)
 {
   cssFile = _cssFile;
 }
 
 static void fill_old_character_definitions(void)
 {
   for (size_t i = 0; i < sizeof(standardchar) / sizeof(CSTRDEF); i++)
   {
     const int nr = standardchar[i].nr;
     const char temp[3] = { char(nr / 256), char(nr % 256), 0 };
     QByteArray name(temp);
     s_characterDefinitionMap.insert(name, StringDefinition(standardchar[i].slen, standardchar[i].st));
   }
 }
 
 static char outbuffer[NULL_TERMINATED(HUGE_STR_MAX)];
 static int no_newline_output = 0;
 static int newline_for_fun = 0;
 static bool output_possible = false;
 
 static const char * const includedirs[] =
 {
   "/usr/include",
   "/usr/include/sys",
   "/usr/local/include",
   "/opt/local/include",
   "/usr/ccs",
   "/usr/X11R6/include",
   "/usr/openwin/include",
   "/usr/include/g++",
-  0
+  nullptr
 };
 
 static bool ignore_links = false;
 
 static void add_links(char *c)
 {
   /*
   ** Add the links to the output.
   ** At the moment the following are recognized:
   **
   ** name(*)                 -> ../man?/name.*
   ** method://string         -> method://string
   ** www.host.name           -> http://www.host.name
   ** ftp.host.name           -> ftp://ftp.host.name
   ** name@host               -> mailto:name@host
   **                 -> file:/usr/include/name.h   (guess)
   **
   ** Other possible links to add in the future:
   **
   ** /dir/dir/file  -> file:/dir/dir/file
   */
 
   if (ignore_links)
   {
     output_real(c);
     return;
   }
 
   int i, j, nr;
   char *f, *g, *h;
   const int numtests = 6; // Nmber of tests
   char *idtest[numtests]; // url, mailto, www, ftp, manpage, C header file
   bool ok;
   /* search for (section) */
   nr = 0;
   idtest[0] = strstr(c + 1, "://");
   idtest[1] = strchr(c + 1, '@');
   idtest[2] = strstr(c, "www.");
   idtest[3] = strstr(c, "ftp.");
   idtest[4] = strchr(c + 1, '(');
   idtest[5] = strstr(c + 1, ".h>");
-  for (i = 0; i < numtests; ++i) nr += (idtest[i] != NULL);
+  for (i = 0; i < numtests; ++i) nr += (idtest[i] != nullptr);
   while (nr)
   {
     j = -1;
     for (i = 0; i < numtests; i++)
       if (idtest[i] && (j < 0 || idtest[i] < idtest[j])) j = i;
     switch (j)
     {
       case 5:   /*  */
       {
         f = idtest[5];
         h = f + 2;
         g = f;
         while (g > c && g[-1] != ';') g--;
         bool wrote_include = false;
 
         if (g != c)
         {
           QByteArray dir;
           QByteArray file(g, h - g);
           file = file.trimmed();
           for (int index = 0; includedirs[index]; index++)
           {
             QByteArray str(includedirs[index]);
             str.append('/');
             str.append(file);
             if (!access(str.data(), R_OK))
             {
               dir = includedirs[index];
               break;
             }
           }
           if (!dir.isEmpty())
           {
 
             char t;
             t = *g;
             *g = 0;
             output_real(c);
             *g = t;
             *h = 0;
 
             QByteArray str;
             str.append("");
             str.append(file.data());
             str.append(">");
 
             output_real(str.data());
             c = f + 6;
             wrote_include = true;
           }
 
         }
 
         if (!wrote_include)
         {
           f[5] = 0;
           output_real(c);
           f[5] = ';';
           c = f + 5;
         }
       }
       break;
       case 4: /* manpage */
         f = idtest[j];
         /* check section */
         g = strchr(f, ')');
         // The character before f must be alphanumeric, the end of a HTML tag or the end of a  
-        if (g != NULL && f > c && (g - f) < 12 && (isalnum(f[-1]) || f[-1] == '>' || (f[-1] == ';')) &&
+        if (g != nullptr && f > c && (g - f) < 12 && (isalnum(f[-1]) || f[-1] == '>' || (f[-1] == ';')) &&
             (isdigit(f[1]) || (f[1] == 'n')) && f[1] != '0' && ((g - f) <= 2 || isalpha(f[2])))
         {
           ok = true;
           h = f + 2;
           while (h < g)
           {
             if (!isalnum(*h++))
             {
               ok = false;
               break;
             }
           }
         }
         else
           ok = false;
 
         h = f - 1;
         if (ok)
         {
           // Skip  
           qCDebug(KIO_MAN_LOG) << "BEFORE SECTION:" <<  *h;
           if ((h > c + 5) && (! memcmp(h - 5, " ", 6)))
           {
             h -= 6;
             qCDebug(KIO_MAN_LOG) << "Skip  ";
           }
           else if ( (h > (c + 6)) && (!memcmp(h - 6, " ", 7)) ) //    narrow space
           {
             h -= 7;
           }
           else if (*h == ';')
           {
             // Not a non-breaking space, so probably not ok
             ok = false;
           }
         }
 
         if (ok)
         {
           /* this might be a link */
           /* skip html makeup */
           while (h > c && *h == '>')
           {
             while (h != c && *h != '<') h--;
             if (h != c) h--;
           }
           if (isalnum(*h))
           {
             char t, sec, *e;
             QByteArray fstr(f);
             e = h + 1;
             sec = f[1];
             const int index = fstr.indexOf(')', 2);
             QByteArray subsec;
             if (index != -1)
               subsec = fstr.mid(2, index - 2);
             else // No closing ')' found, take first character as subsection.
               subsec = fstr.mid(2, 1);
             while (h > c && (isalnum(h[-1]) || h[-1] == '_'
                              || h[-1] == ':' || h[-1] == '-' || h[-1] == '.'))
               h--;
             t = *h;
             *h = '\0';
             output_real(c);
             *h = t;
             t = *e;
             *e = '\0';
             QByteArray str("";
             str += h;
             str += "";
             output_real(str.data());
             *e = t;
             c = e;
           }
         }
         *f = '\0';
         output_real(c);
         *f = '(';
         idtest[4] = f - 1;
         c = f;
         break; /* manpage */
       case 3: /* ftp */
       case 2: /* www */
         g = f = idtest[j];
         while (*g && (isalnum(*g) || *g == '_' || *g == '-' || *g == '+' ||
                       *g == '.' || *g == '/')) g++;
         if (g[-1] == '.') g--;
         if (g - f > 4)
         {
           char t;
           t = *f;
           *f = '\0';
           output_real(c);
           *f = t;
           t = *g;
           *g = '\0';
           QByteArray str;
           str.append("");
           str.append(f);
           str.append("");
           output_real(str.data());
           *g = t;
           c = g;
         }
         else
         {
           f[3] = '\0';
           output_real(c);
           c = f + 3;
           f[3] = '.';
         }
         break;
       case 1: /* mailto */
         g = f = idtest[1];
         while (g > c && (isalnum(g[-1]) || g[-1] == '_' || g[-1] == '-' ||
                          g[-1] == '+' || g[-1] == '.' || g[-1] == '%')) g--;
         if (g - 7 >= c && g[-1] == ':')
         {
           // We have perhaps an email address starting with mailto:
           if (!qstrncmp("mailto:", g - 7, 7))
             g -= 7;
         }
         h = f + 1;
         while (*h && (isalnum(*h) || *h == '_' || *h == '-' || *h == '+' ||
                       *h == '.')) h++;
         if (*h == '.') h--;
         if (h - f > 4 && f - g > 1)
         {
           char t;
           t = *g;
           *g = '\0';
           output_real(c);
           *g = t;
           t = *h;
           *h = '\0';
           QByteArray str;
           str.append("");
           str.append(g);
           str.append("");
           output_real(str.data());
           *h = t;
           c = h;
         }
         else
         {
           *f = '\0';
           output_real(c);
           *f = '@';
           idtest[1] = c;
           c = f;
         }
         break;
       case 0: /* url */
         g = f = idtest[0]; // ://foo...
 
         // backup before :// to get protocol
         while (g > c && isalpha(g[-1]) && islower(g[-1])) g--;
         h = f + 3; // start past ://
         // determine length of path and part of query it looks like...
         while (*h && !isspace(*h) && *h != '<' && *h != '>' && *h != '"' &&
                *h != '&') h++;
         // if protocol length 3-6 characters and path has any length at all...
         // more tests added because this code breaks stylesheet links that use
         // the correct file:/// stuff.
-        if (f - g > 2 && f - g < 7 && h - f > 3 && (strstr(c, "http://") != NULL || strstr(c, "ftp://") != NULL))
+        if (f - g > 2 && f - g < 7 && h - f > 3 && (strstr(c, "http://") != nullptr || strstr(c, "ftp://") != nullptr))
         {
           char t;
           t = *g;
           *g = '\0';
           output_real(c);
           *g = t;
           t = *h;
           *h = '\0';
           QByteArray str;
           str.append("");
           str.append(g);
           str.append("");
           output_real(str.data());
           *h = t;
           c = h;
         }
         else
         {
           f[1] = '\0';
           output_real(c);
           f[1] = '/';
           c = f + 1;
         }
         break;
       default:
         break;
     }
     nr = 0;
     if (idtest[0] && idtest[0] <= c) idtest[0] = strstr(c + 1, "://");
     if (idtest[1] && idtest[1] <= c) idtest[1] = strchr(c + 1, '@');
     if (idtest[2] && idtest[2] < c) idtest[2] = strstr(c, "www.");
     if (idtest[3] && idtest[3] < c) idtest[3] = strstr(c, "ftp.");
     if (idtest[4] && idtest[4] <= c) idtest[4] = strchr(c + 1, '(');
     if (idtest[5] && idtest[5] <= c) idtest[5] = strstr(c + 1, ".h>");
-    for (i = 0; i < numtests; i++) nr += (idtest[i] != NULL);
+    for (i = 0; i < numtests; i++) nr += (idtest[i] != nullptr);
   }
   output_real(c);
 }
 
 static QByteArray current_font;
 static int current_size = 0;
 static int fillout = 1;
 
 static void out_html(const char *c)
 {
   if (!c) return;
 
   // Added, probably due to the const?
   char *c2 = qstrdup(c);
   char *c3 = c2;
 
   static int obp = 0;
 
   if (no_newline_output)
   {
     int i = 0;
     no_newline_output = 1;
     while (c2[i])
     {
       if (!no_newline_output) c2[i-1] = c2[i];
       if (c2[i] == '\n') no_newline_output = 0;
       i++;
     }
     if (!no_newline_output) c2[i-1] = 0;
   }
   if (scaninbuff)
   {
     while (*c2)
     {
       if (buffpos >= buffmax)
       {
         char *h = new char[buffmax*2];
 
         memcpy(h, buffer, buffmax);
         delete [] buffer;
         buffer = h;
         buffmax = buffmax * 2;
       }
       buffer[buffpos++] = *c2++;
     }
   }
   else
     if (output_possible)
     {
       while (*c2)
       {
         outbuffer[obp++] = *c2;
         if (*c2 == '\n' || obp >= HUGE_STR_MAX)
         {
           outbuffer[obp] = '\0';
           add_links(outbuffer);
           obp = 0;
         }
         c2++;
       }
     }
   delete [] c3;
 }
 
 //---------------------------------------------------------------------
 
 void checkListStack()  // see if we need to end a previously begun list item
 {
   if ( !listItemStack.isEmpty() && (listItemStack.size() == itemdepth) )
   {
     out_html("");
   }
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray set_font(const QByteArray& name)
 {
   // Every font but R (Regular) creates  elements
   QByteArray markup;
   if ( (current_font != "R") && (current_font != "P") && !current_font.isEmpty() )
     markup += "";
   const uint len = name.length();
   bool fontok = true;
   if (len == 1)
   {
     const char lead = name[0];
     switch (lead)
     {
       case 'P': // ### TODO: this seems to mean "precedent font"
       case 'R':
         break; // regular, do nothing
       case 'I':
         markup += "";
         break;
       case 'B':
         markup += "";
         break;
       case 'L':
         markup += "";
         break; // ### What's L?
       default:
         fontok = false;
     }
   }
   else if (len == 2)
   {
     if (name == "BI")
       markup += "";
     // Courier
     else if (name == "CR")
       markup += "";
     else if (name == "CW")   // CW is used by pod2man(1) (part of perldoc(1))
       markup += "";
     else if (name == "CI")
       markup += "";
     else if (name == "CB")
       markup += "";
     // Times
     else if (name == "TR")
       markup += "";
     else if (name == "TI")
       markup += "";
     else if (name == "TB")
       markup += "";
     // Helvetica
     else if (name == "HR")
       markup += "";
     else if (name == "HI")
       markup += "";
     else if (name == "HB")
       markup += "";
     else
       fontok = false;
   }
   else if (len == 3)
   {
     if (name == "CBI")
       markup += "";
     else if (name == "TBI")
       markup += "";
     else if (name == "HBI")
       markup += "";
     else
       fontok = false;
   }
   else
     fontok = false;
 
   if (fontok)
     current_font = name;
   else
     current_font = "R"; // Still nothing, then it is 'R' (Regular) // krazy:exclude=doublequote_chars
   return markup;
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray change_to_size(int nr)
 {
   switch (nr)
   {
     case '0':
     case '1':
     case '2':
     case '3':
     case '4':
     case '5':
     case '6':
     case '7':
     case '8':
     case '9':
       nr = nr - '0';
       break;
     case '\0':
       break;
     default:
       nr = current_size + nr;
       if (nr > 9) nr = 9;
       if (nr < -9) nr = -9;
       break;
   }
   if (nr == current_size)
     return "";
   const QByteArray font(current_font);
   QByteArray markup;
   markup = set_font("R");
   if (current_size)
     markup += "";
   current_size = nr;
   if (nr)
   {
     int percent = 100 + nr*1;
     markup += "";
   }
   markup += set_font(font);
   return markup;
 }
 
 //---------------------------------------------------------------------
 
 /* static int asint=0; */
 static int intresult = 0;
 
 static bool skip_escape = false;
 static bool single_escape = false;
 
 static char *scan_escape_direct(char *c, QByteArray& cstr);
 
 /**
  * scan a named character
  * param c position
  */
 static QByteArray scan_named_character(char*& c)
 {
   QByteArray name;
   if (*c == '(')
   {
     // \*(ab  Name of two characters
     if (c[1] == escapesym)
     {
       QByteArray cstr;
       c = scan_escape_direct(c + 2, cstr);
       // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
       name = cstr;
     }
     else
     {
       name += c[1];
       name += c[2];
       c += 3;
     }
   }
   else if (*c == '[')
   {
     // \*[long_name]  Long name
     // Named character groff(7)
     // We must find the ] to get a name
     c++;
     while (*c && *c != ']' && *c != '\n')
     {
       if (*c == escapesym)
       {
         QByteArray cstr;
         c = scan_escape_direct(c + 1, cstr);
         const int result = cstr.indexOf(']');
         if (result == -1)
           name += cstr;
         else
         {
           // Note: we drop the characters after the ]
           name += cstr.left(result);
         }
       }
       else
       {
         name += *c;
         c++;
       }
     }
     if (!*c || *c == '\n')
     {
       qCDebug(KIO_MAN_LOG) << "Found linefeed! Could not parse character name: " << BYTEARRAY(name);
       return "";
     }
     c++;
   }
   else if (*c == 'C' || c[1] == '\'')
   {
     // \C'name'
     c += 2;
     while (*c && *c != '\'' && *c != '\n')
     {
       if (*c == escapesym)
       {
         QByteArray cstr;
         c = scan_escape_direct(c + 1, cstr);
         const int result = cstr.indexOf('\'');
         if (result == -1)
           name += cstr;
         else
         {
           // Note: we drop the characters after the ]
           name += cstr.left(result);
         }
       }
       else
       {
         name += *c;
         c++;
       }
     }
     if (!*c || *c == '\n')
     {
       qCDebug(KIO_MAN_LOG) << "Found linefeed! Could not parse (\\C mode) character name: " << BYTEARRAY(name);
       return "";
     }
     c++;
   }
   // Note: characters with a one character length name do not exist, as they would collide with other escapes
 
   // Now we have the name, let us find it between the string names
   QMap::const_iterator it = s_characterDefinitionMap.constFind(name);
   if (it == s_characterDefinitionMap.constEnd())
   {
     qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find character with name: " << BYTEARRAY(name);
     // No output, as an undefined string is empty by default
     return "";
   }
   else
   {
     qCDebug(KIO_MAN_LOG) << "Character with name: \"" << BYTEARRAY(name) << "\" => " << BYTEARRAY((*it).m_output);
     return (*it).m_output;
   }
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray scan_named_string(char*& c)
 {
   QByteArray name;
   if (*c == '(')
   {
     // \*(ab  Name of two characters
     if (c[1] == escapesym)
     {
       QByteArray cstr;
       c = scan_escape_direct(c + 2, cstr);
       qCDebug(KIO_MAN_LOG) << "\\(" << BYTEARRAY(cstr);
       // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
       name = cstr;
     }
     else
     {
       name += c[1];
       name += c[2];
       c += 3;
     }
   }
   else if (*c == '[')
   {
     // \*[long_name]  Long name
     // Named character groff(7)
     // We must find the ] to get a name
     c++;
     while (*c && *c != ']' && *c != '\n')
     {
       if (*c == escapesym)
       {
         QByteArray cstr;
         c = scan_escape_direct(c + 1, cstr);
         const int result = cstr.indexOf(']');
         if (result == -1)
           name += cstr;
         else
         {
           // Note: we drop the characters after the ]
           name += cstr.left(result);
         }
       }
       else
       {
         name += *c;
         c++;
       }
     }
     if (!*c || *c == '\n')
     {
       qCDebug(KIO_MAN_LOG) << "Found linefeed! Could not parse string name: " << BYTEARRAY(name);
       return "";
     }
     c++;
   }
   else
   {
     // \*a Name of one character
     name += *c;
     c++;
   }
   // Now we have the name, let us find it between the string names
   QMap::const_iterator it = s_stringDefinitionMap.constFind(name);
   if (it == s_stringDefinitionMap.constEnd())
   {
     // try a number register:
     return QByteArray::number(getNumberRegisterValue(name));
 
     //qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find string with name: " << BYTEARRAY(name);
     // No output, as an undefined string is empty by default
     //return "";
   }
   else
   {
     qCDebug(KIO_MAN_LOG) << "String with name: '" << BYTEARRAY(name) << "' => >>>" << BYTEARRAY((*it).m_output) << "<<<";
     return (*it).m_output;
   }
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray scan_dollar_parameter(char*& c)
 {
   int argno = 0; // No dollar argument number yet!
   if (*c == '0')
   {
     //qCDebug(KIO_MAN_LOG) << "$0";
     c++;
     return s_dollarZero;
   }
   else if (*c >= '1' && *c <= '9')
   {
     //qCDebug(KIO_MAN_LOG) << "$ direct";
     argno = (*c - '0');
     c++;
   }
   else if (*c == '(')
   {
     //qCDebug(KIO_MAN_LOG) << "$(";
     if (c[1] && c[2] && c[1] >= '0' && c[1] <= '9' && c[2] >= '0' && c[2] <= '9')
     {
       argno = (c[1] - '0') * 10 + (c[2] - '0');
       c += 3;
     }
     else
     {
       if (!c[1])
         c++;
       else if (!c[2])
         c += 2;
       else
         c += 3;
       return "";
     }
   }
   else if (*c == '[')
   {
     //qCDebug(KIO_MAN_LOG) << "$[";
     argno = 0;
     c++;
     while (*c && *c >= '0' && *c <= '9' && *c != ']')
     {
       argno *= 10;
       argno += (*c - '0');
       c++;
     }
     if (*c != ']')
     {
       return "";
     }
     c++;
   }
   else if ((*c == '*') || (*c == '@'))
   {
     const bool quote = (*c == '@');
     QList::const_iterator it = s_argumentList.constBegin();
     QByteArray param;
     bool space = false;
     for (; it != s_argumentList.constEnd(); ++it)
     {
       if (space)
         param += ' ';
       if (quote)
         param += '\"'; // Not as HTML, as it could be used by macros !
       param += (*it);
       if (quote)
         param += '\"'; // Not as HTML, as it could be used by macros!
       space = true;
     }
     c++;
     return param;
   }
   else
   {
     qCDebug(KIO_MAN_LOG) << "EXCEPTION: unknown parameter $" << *c;
     return "";
   }
   //qCDebug(KIO_MAN_LOG) << "ARG $" << argno;
   if (!s_argumentList.isEmpty() && argno > 0)
   {
     //qCDebug(KIO_MAN_LOG) << "ARG $" << argno << " OK!";
     argno--;
     if (argno >= s_argumentList.size())
     {
       qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find parameter $" << (argno + 1);
       return "";
     }
 
     return s_argumentList[argno];
   }
   return "";
 }
 
 //---------------------------------------------------------------------
 /// return the value of read-only number registers
 
 static int read_only_number_register(const QByteArray& name)
 {
   // Internal read-only variables
   if (name == ".$")
   {
     qCDebug(KIO_MAN_LOG) << "\\n[.$] == " << s_argumentList.size();
     return s_argumentList.size();
   }
   else if (name == ".g")
     return 0; // We are not groff(1)
   else if (name == ".s")
     return current_size;
 #if 0
   // ### TODO: map the fonts to a number
   else if (name == ".f")
     return current_font;
 #endif
   else if (name == ".P")
     return 0; // We are not printing
   else if (name == ".A")
     return s_nroff;
 #ifndef SIMPLE_MAN2HTML
   // Special KDE KIO man:
   const QString version_string(KDE_VERSION_STRING);
   const int version_major = version_string.section('.', 0, 0).toInt();
   const int version_minor = version_string.section('.', 1, 1).toInt();
   const int version_patch = version_string.section('.', 2, 2).toInt();
   if (name == ".KDE_VERSION_MAJOR")
     return version_major;
   else if (name == ".KDE_VERSION_MINOR")
     return version_minor;
   else if (name == ".KDE_VERSION_RELEASE")
     return version_patch;
   else if (name == ".KDE_VERSION")
     return (version_major << 16) | (version_minor << 8) | version_patch;
 #endif
   else if ( name == ".T" )
     return 0;  // Set to 1 in nroff, if -T option used; always 0 in troff.
 
   // ### TODO: groff defines many more read-only number registers
   qCDebug(KIO_MAN_LOG) << "EXCEPTION: unknown read-only number register: " << BYTEARRAY(name);
 
   return 0; // Undefined variable
 
 }
 
 //---------------------------------------------------------------------
 
 static int getNumberRegisterValue(const QByteArray &name, int sign)
 {
   if (name[0] == '.')
   {
     return read_only_number_register(name);
   }
   else
   {
     QMap< QByteArray, NumberDefinition >::iterator it = s_numberDefinitionMap.find(name);
     if (it == s_numberDefinitionMap.end())
     {
       return 0; // Undefined variable
     }
     else
     {
       (*it).m_value += sign * (*it).m_increment;
       return (*it).m_value;
     }
   }
 }
 
 //---------------------------------------------------------------------
 /// get the value of a number register and auto-increment if asked
 
 static int scan_number_register(char*& c)
 {
   int sign = 0; // Sign for auto-increment (if any)
   switch (*c)
   {
     case '+':
       sign = 1;
       c++;
       break;
     case '-':
       sign = -1;
       c++;
       break;
     default:
       break;
   }
   QByteArray name;
   if (*c == '[')
   {
     c++;
     if (*c == '+')
     {
       sign = 1;
       c++;
     }
     else if (*c == '-')
     {
       sign = -1;
       c++;
     }
     while (*c && *c != ']' && *c != '\n')
     {
       // ### TODO: a \*[string] could be inside and should be processed
       name += *c;
       c++;
     }
     if (!*c || *c == '\n')
     {
       qCDebug(KIO_MAN_LOG) << "Found linefeed! Could not parse number register name: " << BYTEARRAY(name);
       return 0;
     }
     c++;
   }
   else if (*c == '(')
   {
     c++;
     if (*c == '+')
     {
       sign = 1;
       c++;
     }
     else if (*c == '-')
     {
       sign = -1;
       c++;
     }
     name += c[0];
     name += c[1];
     c += 2;
   }
   else
   {
     name += *c;
     c++;
   }
 
   return getNumberRegisterValue(name, sign);
 }
 
 //---------------------------------------------------------------------
 // scan a name from the following
 // x     ... return x    (one char)
 // (xx   ... return xx   (two chars)
 // [xxx] ... return xxx  (any chars)
 // after scanning, c points to the terminating char (0, \n or ])
 
 static QByteArray scan_name(char *&c)
 {
   QByteArray name;
   if ( *c == '(' )
   {
     int i = 0;
     for (c++; *c && (*c != '\n') && (i < 2); c++, i++)
       name += *c;
   }
   else if ( *c == '[' )
   {
     for (c++; *c && (*c != ']') && (*c != '\n'); c++)
       name += *c;
   }
   else
     name += *c;
 
   return name;
 }
 
 //---------------------------------------------------------------------
 /// get and set font
 
 static QByteArray scan_named_font(char*& c)
 {
   QByteArray name;
   if (*c == '(')
   {
     // \f(ab  Name of two characters
     if (c[1] == escapesym)
     {
       QByteArray cstr;
       c = scan_escape_direct(c + 2, cstr);
       qCDebug(KIO_MAN_LOG) << "\\(" << BYTEARRAY(cstr);
       // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used.
       name = cstr;
     }
     else
     {
       name += c[1];
       name += c[2];
       c += 3;
     }
   }
   else if (*c == '[')
   {
     // \f[long_name]  Long name
     // We must find the ] to get a name
     c++;
     while (*c && *c != ']' && *c != '\n')
     {
       if (*c == escapesym)
       {
         QByteArray cstr;
         c = scan_escape_direct(c + 1, cstr);
         const int result = cstr.indexOf(']');
         if (result == -1)
           name += cstr;
         else
         {
           // Note: we drop the characters after the ]
           name += cstr.left(result);
         }
       }
       else
       {
         name += *c;
         c++;
       }
     }
     if (!*c || *c == '\n')
     {
       qCDebug(KIO_MAN_LOG) << "Found linefeed! Could not parse font name: " << BYTEARRAY(name);
       return "";
     }
     c++;
   }
   else if ( *c )  // \f alone makes c point at 0-byte
   {
     // \fa Font name with one character or one digit
     // ### HACK do *not* use:  name = *c;  or name would be empty
     name += *c;
     c++;
   }
   //qCDebug(KIO_MAN_LOG) << "FONT NAME: " << BYTEARRAY( name );
   // Now we have the name, let us find the font
   bool ok = false;
   const unsigned int number = name.toUInt(&ok);
   if (ok)
   {
     if (number < 5)
     {
       const char* const fonts[] = { "R", "I", "B", "BI", "CR" }; // Regular, Italic, Bold, Bold Italic, Courier regular
       name = fonts[ number ];
     }
     else
     {
       qCDebug(KIO_MAN_LOG) << "EXCEPTION: font has too big number: " << BYTEARRAY(name) << " => " << number;
       name = "R"; // Let assume Regular // krazy:exclude=doublequote_chars
     }
   }
   else if (name.isEmpty())
   {
     qCDebug(KIO_MAN_LOG) << "EXCEPTION: font has no name => using R";
     name = "R"; // Let assume Regular // krazy:exclude=doublequote_chars
   }
   if (!skip_escape)
     return set_font(name);
   else
     return "";
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray scan_number_code(char*& c)
 {
   QByteArray number;
   if (*c != '\'')
     return "";
   c++; // Go past the opening single quote
   while (*c && (*c != '\n') && (*c != '\''))
   {
     number += *c;
     c++;
   }
   bool ok = false;
   unsigned int result = number.toUInt(&ok);
   if ((result < ' ') || (result > 65535))
     return "";
   else if (result == '\t')
   {
     curpos += 8;
     curpos &= 0xfff8;
     return "\t";
   }
   number.setNum(result);
   number.prepend("&#");
   number.append(";");
   curpos ++;
   c++; // Go past the closing single quote
   return number;
 }
 
 //---------------------------------------------------------------------
 // ### TODO known missing escapes from groff(7):
 // ### TODO \R
 
 static char *scan_escape_direct(char *c, QByteArray& cstr)
 {
   bool exoutputp;
   bool exskipescape;
   int i, j;
   bool cplusplus = true; // Should the c++ call be executed at the end of the function
 
   cstr.clear();
   intresult = 0;
   switch (*c)
   {
     case 'e':
       cstr += escapesym;
       curpos++;
       break;
     case '0': // space of digit width
       cstr = " ";  // Unicode FIGURE SPACE
       curpos++;
       break;
     case '~': // non-breakable-space (resizeable!)
     case ' ':
       cstr = " ";
       curpos++;
       break;
     case '|': // half-non-breakable-space
     case '^': // quarter-non-breakable-space
       cstr = " ";  // Unicode NARROW NO-BREAK SPACE
       curpos++;
       break;
     case ':':
       break;  // ignore optional line break
     case ',':
       break;  //  left italic correction, always a zero motion
     case '/':
       cstr = " ";  // Unicode THIN SPACE
       curpos++;
       break;  // italic correction, i.e. a small piece of horizontal motion
     case '"': // comment. skip rest of line
       for (c++; *c && (*c != '\n'); c++) ;
       cplusplus = false;
       break;
       // ### TODO \# like \" but does not ignore the end of line (groff(7))
     case '$':
     {
       c++;
       cstr = scan_dollar_parameter(c);
       cplusplus = false;
       break;
     }
     case 'z':
     {
       c++;
       if (*c == '\\')
       {
         c = scan_escape_direct(c + 1, cstr);
         c--;
       }
       else
         cstr = QByteArray(c, 1);
       break;
     }
     case 'k':
     {
       // Store the current horizontal position in the _input_ line in
       // number register with name POSITION
       c++;
       cstr = scan_name(c);
       cstr.clear(); // TODO not implemented; discard it
       break;
     }
     case '!':
     case '%':
     case 'a':
     case 'd':
     case 'r':
     case 'u':
     case '\n':
     case '&': // Non-printing, zero width character
     case ')': // Transparent non-printing zero width character
       break;
     case '(':
     case '[':
     case 'C':
     {
       // Do not go forward as scan_named_character needs the leading symbol
       cstr = scan_named_character(c);
       cplusplus = false;
       break;
     }
     case '*':
     {
       c++;
       cstr = scan_named_string(c);
       cplusplus = false;
       break;
     }
     case 'f':
     {
       c++;
       cstr = scan_named_font(c);
       cplusplus = false;
       break;
     }
     case 'F':  // font family
     {
       c++;
       cstr = scan_name(c);
 
       if ( cstr == "C" )
         cstr = set_font("CR");
       else if ( cstr == "T" )
         cstr = set_font("TR");
       else if ( cstr == "H" )
         cstr = set_font("HR");
       else
         cstr = set_font(cstr);
 
       break;
     }
     case 'm': // color
     {
       c++;
       cstr = scan_name(c);
 
       if ( cstr.isEmpty() )
         cstr = "";
       else
         cstr = "";
 
       break;
     }
     case 's': // ### FIXME: many forms are missing
       c++;
       j = 0;
       i = 0;
       if (*c == '-')
       {
         j = -1;
         c++;
       }
       else if (*c == '+')
       {
         j = 1;
         c++;
       }
       if (*c == '0') c++;
       else if (*c == '\\')
       {
         c++;
         c = scan_escape_direct(c, cstr);
         i = intresult;
         if (!j) j = 1;
       }
       else
         while (isdigit(*c) && (!i || (!j && i < 4))) i = i * 10 + (*c++) - '0';
       if (!j)
       {
         j = 1;
         if (i) i = i - 10;
       }
       if (!skip_escape) cstr = change_to_size(i * j);
       c--;
       break;
     case 'n':
     {
       c++;
       intresult = scan_number_register(c);
       cplusplus = false;
       break;
     }
     case 'w':
       c++;
       i = *c;
       c++;
       exoutputp = output_possible;
       exskipescape = skip_escape;
       output_possible = false;
       skip_escape = true;
       j = 0;
       while (*c != i)
       {
         j++;
         if (*c == escapesym)
           c = scan_escape_direct(c + 1, cstr);
         else
           c++;
       }
       output_possible = exoutputp;
       skip_escape = exskipescape;
       intresult = j;
       break;
     case 'l':
       cstr = "
"; curpos = 0; case 'b': case 'v': case 'x': case 'o': case 'L': case 'h': c++; i = *c; c++; exoutputp = output_possible; exskipescape = skip_escape; output_possible = 0; skip_escape = true; while (*c != i) if (*c == escapesym) c = scan_escape_direct(c + 1, cstr); else c++; output_possible = exoutputp; skip_escape = exskipescape; break; case 'c': no_newline_output = 1; break; case '{': newline_for_fun++; break; // Start conditional block case '}': if (newline_for_fun) newline_for_fun--; break; // End conditional block case 'p': cstr = "
\n"; curpos = 0; break; case 't': cstr = "\t"; curpos = (curpos + 8) & 0xfff8; break; case '<': cstr = "<"; curpos++; break; case '>': cstr = ">"; curpos++; break; case '\\': { if (single_escape) c--; else cstr = "\\"; break; } case 'N': { c++; cstr = scan_number_code(c); cplusplus = false; break; } case '\'': cstr = "´"; curpos++; break; // groff(7) ### TODO verify case '`': cstr = "`"; // krazy:exclude=doublequote_chars curpos++; break; // groff(7) case '-': cstr = "-"; // krazy:exclude=doublequote_chars curpos++; break; // groff(7) case '.': cstr = "."; // krazy:exclude=doublequote_chars curpos++; break; // groff(7) default: cstr = QByteArray(c, 1); curpos++; break; } if (cplusplus && *c) c++; return c; } //--------------------------------------------------------------------- static char *scan_escape(char *c) { QByteArray cstr; char* result = scan_escape_direct(c, cstr); if (!skip_escape) out_html(cstr); return result; } //--------------------------------------------------------------------- class TABLEROW; class TABLEITEM { public: TABLEITEM(TABLEROW *row); ~TABLEITEM() { delete [] contents; } void setContents(const char *_contents) { delete [] contents; contents = qstrdup(_contents); } const char *getContents() const { return contents; } void init() { delete [] contents; - contents = 0; + contents = nullptr; size = 0; align = 0; valign = 0; colspan = 1; rowspan = 1; font = 0; vleft = 0; vright = 0; space = 0; width = 0; } void copyLayout(const TABLEITEM *orig) { size = orig->size; align = orig->align; valign = orig->valign; colspan = orig->colspan; rowspan = orig->rowspan; font = orig->font; vleft = orig->vleft; vright = orig->vright; space = orig->space; width = orig->width; } public: int size, align, valign, colspan, rowspan, font, vleft, vright, space, width; private: char *contents; TABLEROW *_parent; }; class TABLEROW { char *test; public: TABLEROW() { test = new char; - prev = 0; - next = 0; + prev = nullptr; + next = nullptr; } ~TABLEROW() { qDeleteAll(items); items.clear(); delete test; } int length() const { return items.count(); } bool has(int index) { return (index >= 0) && (index < (int)items.count()); } TABLEITEM &at(int index) { return *items.at(index); } TABLEROW *copyLayout() const; void addItem(TABLEITEM *item) { items.append(item); } TABLEROW *prev, *next; private: QList items; }; -TABLEITEM::TABLEITEM(TABLEROW *row) : contents(0), _parent(row) +TABLEITEM::TABLEITEM(TABLEROW *row) : contents(nullptr), _parent(row) { init(); _parent->addItem(this); } TABLEROW *TABLEROW::copyLayout() const { TABLEROW *newrow = new TABLEROW(); QListIterator it(items); while (it.hasNext()) { TABLEITEM *newitem = new TABLEITEM(newrow); newitem->copyLayout(it.next()); } return newrow; } static const char * const tableopt[] = { "center", "expand", "box", "allbox", "doublebox", "tab", "linesize", - "delim", NULL + "delim", nullptr }; static const int tableoptl[] = { 6, 6, 3, 6, 9, 3, 8, 5, 0}; static void clear_table(TABLEROW *table) { TABLEROW *tr1, *tr2; tr1 = table; while (tr1->prev) tr1 = tr1->prev; while (tr1) { tr2 = tr1; tr1 = tr1->next; delete tr2; } } static char *scan_expression(char *c, int *result); static char *scan_format(char *c, TABLEROW **result, int *maxcol) { TABLEROW *layout, *currow; TABLEITEM *curfield; int i, j; if (*result) { clear_table(*result); } layout = currow = new TABLEROW(); curfield = new TABLEITEM(currow); while (*c && *c != '.') { switch (*c) { case 'C': case 'c': case 'N': case 'n': case 'R': case 'r': case 'A': case 'a': case 'L': case 'l': case 'S': case 's': case '^': case '_': if (curfield->align) curfield = new TABLEITEM(currow); curfield->align = toupper(*c); c++; break; case 'i': case 'I': case 'B': case 'b': curfield->font = toupper(*c); c++; break; case 'f': case 'F': c++; curfield->font = toupper(*c); c++; if (!isspace(*c) && *c != '.') c++; break; case 't': case 'T': curfield->valign = 't'; c++; break; case 'p': case 'P': c++; i = j = 0; if (*c == '+') { j = 1; c++; } if (*c == '-') { j = -1; c++; } while (isdigit(*c)) i = i * 10 + (*c++) - '0'; if (j) curfield->size = i * j; else curfield->size = j - 10; break; case 'v': case 'V': case 'w': case 'W': c = scan_expression(c + 2, &curfield->width); break; case '|': if (curfield->align) curfield->vleft++; else curfield->vright++; c++; break; case 'e': case 'E': c++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i = 0; while (isdigit(*c)) i = i * 10 + (*c++) - '0'; curfield->space = i; break; case ',': case '\n': currow->next = new TABLEROW(); currow->next->prev = currow; currow = currow->next; - currow->next = NULL; + currow->next = nullptr; curfield = new TABLEITEM(currow); c++; break; default: c++; break; } } if (*c == '.') while (*c++ != '\n'); *maxcol = 0; currow = layout; while (currow) { i = currow->length(); if (i > *maxcol) *maxcol = i; currow = currow->next; } *result = layout; return c; } static TABLEROW *next_row(TABLEROW *tr) { if (tr->next) { tr = tr->next; if (!tr->next) return next_row(tr); return tr; } else { tr->next = tr->copyLayout(); tr->next->prev = tr; return tr->next; } } static char itemreset[20] = "\\fR\\s0"; #define FORWARDCUR do { curfield++; } while (currow->has(curfield) && currow->at(curfield).align=='S'); static char *scan_table(char *c) { char *h; char *g; int center = 0, expand = 0, box = 0, border = 0, linesize = 1; int i, j, maxcol = 0, finished = 0; QByteArray oldfont; int oldsize, oldfillout; char itemsep = '\t'; - TABLEROW *layout = NULL, *currow; + TABLEROW *layout = nullptr, *currow; int curfield = -1; while (*c++ != '\n'); h = c; if (*h == '.') return c -1; oldfont = current_font; oldsize = current_size; oldfillout = fillout; out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout = 1; out_html("
"); } while (*h && *h != '\n') h++; if (h[-1] == ';') { /* scan table options */ while (c < h) { while (isspace(*c)) c++; for (i = 0; tableopt[i] && qstrncmp(tableopt[i], c, tableoptl[i]);i++); c = c + tableoptl[i]; switch (i) { case 0: center = 1; break; case 1: expand = 1; break; case 2: box = 1; break; case 3: border = 1; break; case 4: box = 2; break; case 5: while (*c++ != '('); itemsep = *c++; break; case 6: while (*c++ != '('); linesize = 0; while (isdigit(*c)) linesize = linesize * 10 + (*c++) - '0'; break; case 7: while (*c != ')') c++; default: break; } c++; } c = h + 1; } /* scan layout */ c = scan_format(c, &layout, &maxcol); // currow=layout; currow = next_row(layout); curfield = 0; i = 0; while (!finished && *c) { /* search item */ h = c; if ((*c == '_' || *c == '=') && (c[1] == itemsep || c[1] == '\n')) { if (c[-1] == '\n' && c[1] == '\n') { if (currow->prev) { currow->prev->next = new TABLEROW(); currow->prev->next->next = currow; currow->prev->next->prev = currow->prev; currow->prev = currow->prev->next; } else { currow->prev = layout = new TABLEROW(); - currow->prev->prev = NULL; + currow->prev->prev = nullptr; currow->prev->next = currow; } TABLEITEM *newitem = new TABLEITEM(currow->prev); newitem->align = *c; newitem->colspan = maxcol; curfield = 0; c = c + 2; } else { if (currow->has(curfield)) { currow->at(curfield).align = *c; FORWARDCUR; } if (c[1] == '\n') { currow = next_row(currow); curfield = 0; } c = c + 2; } } else if (*c == 'T' && c[1] == '{') { h = c + 2; c = strstr(h, "\nT}"); c++; *c = '\0'; - g = NULL; + g = nullptr; scan_troff(h, 0, &g); scan_troff(itemreset, 0, &g); *c = 'T'; c += 3; if (currow->has(curfield)) { currow->at(curfield).setContents(g); FORWARDCUR; } delete [] g; if (c[-1] == '\n') { currow = next_row(currow); curfield = 0; } } else if (*c == '.' && c[1] == 'T' && c[2] == '&' && c[-1] == '\n') { TABLEROW *hr; while (*c++ != '\n'); hr = currow; currow = currow->prev; - hr->prev = NULL; + hr->prev = nullptr; c = scan_format(c, &hr, &i); hr->prev = currow; currow->next = hr; currow = hr; next_row(currow); curfield = 0; } else if (*c == '.' && c[1] == 'T' && c[2] == 'E' && c[-1] == '\n') { finished = 1; while (*c++ != '\n'); if (currow->prev) - currow->prev->next = NULL; - currow->prev = NULL; + currow->prev->next = nullptr; + currow->prev = nullptr; clear_table(currow); - currow = 0; + currow = nullptr; } else if (*c == '.' && c[-1] == '\n' && !isdigit(c[1])) { /* skip troff request inside table (usually only .sp ) */ while (*c++ != '\n'); } else { h = c; while (*c && (*c != itemsep || c[-1] == '\\') && (*c != '\n' || c[-1] == '\\')) c++; i = 0; if (*c == itemsep) { i = 1; *c = '\n'; } if (h[0] == '\\' && h[2] == '\n' && (h[1] == '_' || h[1] == '^')) { if (currow->has(curfield)) { currow->at(curfield).align = h[1]; FORWARDCUR; } h = h + 3; } else { - g = NULL; + g = nullptr; h = scan_troff(h, 1, &g); scan_troff(itemreset, 0, &g); if (currow->has(curfield)) { currow->at(curfield).setContents(g); FORWARDCUR; } delete [] g; } if (i) *c = itemsep; c = h; if (c[-1] == '\n') { currow = next_row(currow); curfield = 0; } } } /* calculate colspan and rowspan */ currow = layout; while (currow->next) currow = currow->next; while (currow) { int ti = 0, ti1 = 0, ti2 = -1; TABLEROW *prev = currow->prev; if (!prev) break; while (prev->has(ti1)) { if (currow->has(ti)) switch (currow->at(ti).align) { case 'S': if (currow->has(ti2)) { currow->at(ti2).colspan++; if (currow->at(ti2).rowspan < prev->at(ti1).rowspan) currow->at(ti2).rowspan = prev->at(ti1).rowspan; } break; case '^': if (prev->has(ti1)) prev->at(ti1).rowspan++; default: if (ti2 < 0) ti2 = ti; else { do { ti2++; } while (currow->has(ti2) && currow->at(ti2).align == 'S'); } break; } ti++; if (ti1 >= 0) ti1++; } currow = currow->prev; } /* produce html output */ if (center) out_html("
"); if (box == 2) out_html(""); curfield = 0; while (currow->has(curfield)) { if (currow->at(curfield).align != 'S' && currow->at(curfield).align != '^') { out_html(""); } curfield++; } out_html("\n"); currow = currow->next; } clear_table(layout); if (box && !border) out_html("
"); out_html("
\n"); currow = layout; while (currow) { j = 0; out_html("
at(curfield).align) { case 'N': currow->at(curfield).space += 4; case 'R': out_html(" ALIGN=right"); break; case 'C': out_html(" ALIGN=center"); default: break; } if (!currow->at(curfield).valign && currow->at(curfield).rowspan > 1) out_html(" VALIGN=center"); if (currow->at(curfield).colspan > 1) { out_html(" COLSPAN="); out_html(QByteArray::number(currow->at(curfield).colspan)); } if (currow->at(curfield).rowspan > 1) { out_html(" ROWSPAN="); out_html(QByteArray::number(currow->at(curfield).rowspan)); } j = j + currow->at(curfield).colspan; out_html(">"); if (currow->at(curfield).size) out_html(change_to_size(currow->at(curfield).size)); if (currow->at(curfield).font) out_html(set_font(QByteArray::number(currow->at(curfield).font))); switch (currow->at(curfield).align) { case '=': out_html("

"); break; case '_': out_html("
"); break; default: out_html(currow->at(curfield).getContents()); break; } if (currow->at(curfield).space) for (i = 0; i < currow->at(curfield).space;i++) out_html(" "); if (currow->at(curfield).font) out_html(set_font("R")); if (currow->at(curfield).size) out_html(change_to_size(0)); if (j >= maxcol && currow->at(curfield).align > '@' && currow->at(curfield).align != '_') out_html("
"); out_html("
"); out_html(""); if (box == 2) out_html(""); if (center) out_html("
\n"); else out_html("\n"); if (!oldfillout) out_html("
");
   fillout = oldfillout;
   out_html(change_to_size(oldsize));
   out_html(set_font(oldfont));
   return c;
 }
 
 //---------------------------------------------------------------------
 
 static char *scan_expression(char *c, int *result, const unsigned int numLoop)
 {
   int value = 0, value2, sign = 1, opex = 0;
   char oper = 'c';
 
   if (*c == '!')
   {
     c = scan_expression(c + 1, &value);
     value = (!value);
   }
   else if (*c == 'n')
   {
     c++;
     value = s_nroff;
   }
   else if (*c == 't')
   {
     c++;
     value = 1 - s_nroff;
   }
   else if (*c == '\'' || *c == '"' || *c < ' ' || (*c == '\\' && c[1] == '('))
   {
     /* ?string1?string2?
     ** test if string1 equals string2.
     */
-    char *st1 = NULL, *st2 = NULL, *h;
-    char *tcmp = NULL;
+    char *st1 = nullptr, *st2 = nullptr, *h;
+    char *tcmp = nullptr;
     char sep;
     sep = *c;
     if (sep == '\\')
     {
       tcmp = c;
       c = c + 3;
     }
     c++;
     h = c;
     while (*c != sep && (!tcmp || qstrncmp(c, tcmp, 4))) c++;
     *c = '\n';
     scan_troff(h, 1, &st1);
     *c = sep;
     if (tcmp) c = c + 3;
     c++;
     h = c;
     while (*c != sep && (!tcmp || qstrncmp(c, tcmp, 4))) c++;
     *c = '\n';
     scan_troff(h, 1, &st2);
     *c = sep;
     if (!st1 && !st2) value = 1;
     else if (!st1 || !st2) value = 0;
     else value = (!qstrcmp(st1, st2));
     delete [] st1;
     delete [] st2;
     if (tcmp) c = c + 3;
     c++;
   }
   else
   {
     while (*c && (!isspace(*c) || (numLoop > 0)) && *c != ')' && opex >= 0)
     {
       opex = 0;
       switch (*c)
       {
         case '(':
           c = scan_expression(c + 1, &value2, numLoop + 1);
           value2 = sign * value2;
           opex = 1;
           break;
         case '.':
         case '0':
         case '1':
         case '2':
         case '3':
         case '4':
         case '5':
         case '6':
         case '7':
         case '8':
         case '9':
         {
           int num = 0, denum = 1;
           value2 = 0;
           while (isdigit(*c)) value2 = value2 * 10 + ((*c++) - '0');
           if (*c == '.' && isdigit(c[1]))
           {
             c++;
             while (isdigit(*c))
             {
               num = num * 10 + ((*c++) - '0');
               denum = denum * 10;
             }
           }
           if (isalpha(*c))
           {
             /* scale indicator */
             switch (*c)
             {
               case 'i': /* inch -> 10pt */
                 value2 = value2 * 10 + (num * 10 + denum / 2) / denum;
                 num = 0;
                 break;
               default:
                 break;
             }
             c++;
           }
           value2 = value2 + (num + denum / 2) / denum;
           value2 = sign * value2;
           opex = 1;
           if (*c == '.')
             opex = -1;
 
         }
         break;
         case '\\':
           c = scan_escape(c + 1);
           value2 = intresult * sign;
           if (isalpha(*c)) c++; /* scale indicator */
           opex = 1;
           break;
         case '-':
           if (oper)
           {
             sign = -1;
             c++;
             break;
           }
         case '>':
         case '<':
         case '+':
         case '/':
         case '*':
         case '%':
         case '&':
         case '=':
         case ':':
           if (c[1] == '=') oper = (*c++) + 16;
           else oper = *c;
           c++;
           break;
         default:
           c++;
           break;
       }
       if (opex > 0)
       {
         sign = 1;
         switch (oper)
         {
           case 'c':
             value = value2;
             break;
           case '-':
             value = value - value2;
             break;
           case '+':
             value = value + value2;
             break;
           case '*':
             value = value * value2;
             break;
           case '/':
             if (value2) value = value / value2;
             break;
           case '%':
             if (value2) value = value % value2;
             break;
           case '<':
             value = (value < value2);
             break;
           case '>':
             value = (value > value2);
             break;
           case '>'+16:
             value = (value >= value2);
             break;
           case '<'+16:
             value = (value <= value2);
             break;
           case '=':
           case '='+16:
             value = (value == value2);
             break;
           case '&':
             value = (value && value2);
             break;
           case ':':
             value = (value || value2);
             break;
           default:
           {
             qCDebug(KIO_MAN_LOG) << "Unknown operator " << char(oper);
           }
         }
         oper = 0;
       }
     }
     if (*c == ')') c++;
   }
   *result = value;
   return c;
 }
 
 static char *scan_expression(char *c, int *result)
 {
   return scan_expression(c, result, 0);
 }
 
 static void trans_char(char *c, char s, char t)
 {
   char *sl = c;
   int slash = 0;
   while (*sl != '\n' || slash)
   {
     if (!slash)
     {
       if (*sl == escapesym)
         slash = 1;
       else if (*sl == s)
         *sl = t;
     }
     else slash = 0;
     sl++;
   }
 }
 
 //---------------------------------------------------------------------
 // parse 1 line (or a line which stretches multiple lines by \(enter) )
 // return all arguments starting at \p c in \p args
 // returns the pointer to the next char where scanning should continue
 // (which is the char after the ending \n)
 // argPointers .. a list of pointers to the startchars of each arg pointing into the string given with c
 
-void getArguments(/* const */ char *&c, QList &args, QList *argPointers = 0)
+void getArguments(/* const */ char *&c, QList &args, QList *argPointers = nullptr)
 {
   args.clear();
   if ( argPointers )
     argPointers->clear();
 
   QByteArray arg;
   arg.reserve(30);  // reduce num of reallocs
   bool inString = false;
   bool inArgument = false;
 
   for (; *c && (*c != '\n'); c++)
   {
     if ( *c == '"' )
     {
       if ( !inString )
       {
         inString = true;  // start of quoted argument
       }
       else
       {
         // according to http://heirloom.sourceforge.net/doctools/troff.pdf chapter 7.3
         // two consecutive quotes inside a string is one quote char
         if ( *(c+1) == '"' )
         {
           arg += '"';
           c++;
         }
         else  // end of quoted argument
         {
           args.append(arg);
           arg.clear();
           inString = false;
           inArgument = false;
         }
       }
     }
     else if ( *c == ' ' )
     {
       if ( inString )
       {
         arg += *c;
         if ( !inArgument )  // argument not yet found (leading spaces)
         {
           inArgument = true;
 
           if ( argPointers )
             argPointers->append(c);
         }
       }
       else if ( inArgument )
       {
         // end of previous argument
         args.append(arg);
         arg.clear();
         inArgument = false;
       }
     }
     else if ( (*c == escapesym) && (*(c+1) == ' ') )
     {
       // special handling \ shall be kept as is
       arg += *c++;
       arg += *c;
 
       if ( !inArgument )  // argument not yet found (leading spaces)
       {
         inArgument = true;
 
         if ( argPointers )
           argPointers->append(c);
       }
     }
     else if ( (*c == escapesym) && (*(c+1) == '\n') )
     {
       c++;
     }
     else if ( (*c == escapesym) && (*(c+1) == '"') )  // start of comment; skip rest of line
     {
       if ( inArgument )
       {
         // end of previous argument
         args.append(arg);
         arg.clear();
         inArgument = false;
       }
 
       // skip rest of line
       while ( *c && (*c != '\n') ) c++;
       break;
     }
     else if ( *c != ' ' )
     {
       arg += *c;
       if ( !inArgument )  // argument not yet found (leading spaces)
       {
         inArgument = true;
 
         if ( argPointers )
           argPointers->append(c);
       }
     }
   }
 
   if ( inArgument )
   {
     // end of previous argument
     args.append(arg);
   }
 
   if ( *c ) c++;
 
 #if 0
   for (int i = 0; i < args.count(); i++)
   {
     qWarning("ARG:%d >>>%s<<<", i, args[i].data());
   }
 #endif
 }
 
 //---------------------------------------------------------------------
 
 static const char * const abbrev_list[] =
 {
   "GSBG", "Getting Started ",
   "SUBG", "Customizing SunOS",
   "SHBG", "Basic Troubleshooting",
   "SVBG", "SunView User's Guide",
   "MMBG", "Mail and Messages",
   "DMBG", "Doing More with SunOS",
   "UNBG", "Using the Network",
   "GDBG", "Games, Demos & Other Pursuits",
   "CHANGE", "SunOS 4.1 Release Manual",
   "INSTALL", "Installing SunOS 4.1",
   "ADMIN", "System and Network Administration",
   "SECUR", "Security Features Guide",
   "PROM", "PROM User's Manual",
   "DIAG", "Sun System Diagnostics",
   "SUNDIAG", "Sundiag User's Guide",
   "MANPAGES", "SunOS Reference Manual",
   "REFMAN", "SunOS Reference Manual",
   "SSI", "Sun System Introduction",
   "SSO", "System Services Overview",
   "TEXT", "Editing Text Files",
   "DOCS", "Formatting Documents",
   "TROFF", "Using nroff and troff",
   "INDEX", "Global Index",
   "CPG", "C Programmer's Guide",
   "CREF", "C Reference Manual",
   "ASSY", "Assembly Language Reference",
   "PUL", "Programming Utilities and Libraries",
   "DEBUG", "Debugging Tools",
   "NETP", "Network Programming",
   "DRIVER", "Writing Device Drivers",
   "STREAMS", "STREAMS Programming",
   "SBDK", "SBus Developer's Kit",
   "WDDS", "Writing Device Drivers for the SBus",
   "FPOINT", "Floating-Point Programmer's Guide",
   "SVPG", "SunView 1 Programmer's Guide",
   "SVSPG", "SunView 1 System Programmer's Guide",
   "PIXRCT", "Pixrect Reference Manual",
   "CGI", "SunCGI Reference Manual",
   "CORE", "SunCore Reference Manual",
   "4ASSY", "Sun-4 Assembly Language Reference",
   "SARCH", "SPARC Architecture Manual",
   "KR", "The C Programming Language",
-  NULL, NULL
+  nullptr, nullptr
 };
 
 static const char *lookup_abbrev(const char *c)
 {
   int i = 0;
 
   if (!c) return "";
   while (abbrev_list[i] && qstrcmp(c, abbrev_list[i])) i = i + 2;
   if (abbrev_list[i])
     return abbrev_list[i+1];
   else
     return c;
 }
 
 //---------------------------------------------------------------------
 
 static const char * const section_list[] =
 {
 #ifdef Q_OS_SOLARIS
   // for Solaris
   "1", "User Commands",
   "1B", "SunOS/BSD Compatibility Package Commands",
   "1b", "SunOS/BSD Compatibility Package Commands",
   "1C", "Communication Commands ",
   "1c", "Communication Commands",
   "1F", "FMLI Commands ",
   "1f", "FMLI Commands",
   "1G", "Graphics and CAD Commands ",
   "1g", "Graphics and CAD Commands ",
   "1M", "Maintenance Commands",
   "1m", "Maintenance Commands",
   "1S", "SunOS Specific Commands",
   "1s", "SunOS Specific Commands",
   "2", "System Calls",
   "3", "C Library Functions",
   "3B", "SunOS/BSD Compatibility Library Functions",
   "3b", "SunOS/BSD Compatibility Library Functions",
   "3C", "C Library Functions",
   "3c", "C Library Functions",
   "3E", "C Library Functions",
   "3e", "C Library Functions",
   "3F", "Fortran Library Routines",
   "3f", "Fortran Library Routines",
   "3G", "C Library Functions",
   "3g", "C Library Functions",
   "3I", "Wide Character Functions",
   "3i", "Wide Character Functions",
   "3K", "Kernel VM Library Functions",
   "3k", "Kernel VM Library Functions",
   "3L", "Lightweight Processes Library",
   "3l", "Lightweight Processes Library",
   "3M", "Mathematical Library",
   "3m", "Mathematical Library",
   "3N", "Network Functions",
   "3n", "Network Functions",
   "3R", "Realtime Library",
   "3r", "Realtime Library",
   "3S", "Standard I/O Functions",
   "3s", "Standard I/O Functions",
   "3T", "Threads Library",
   "3t", "Threads Library",
   "3W", "C Library Functions",
   "3w", "C Library Functions",
   "3X", "Miscellaneous Library Functions",
   "3x", "Miscellaneous Library Functions",
   "4", "File Formats",
   "4B", "SunOS/BSD Compatibility Package File Formats",
   "4b", "SunOS/BSD Compatibility Package File Formats",
   "5", "Headers, Tables, and Macros",
   "6", "Games and Demos",
   "7", "Special Files",
   "7B", "SunOS/BSD Compatibility Special Files",
   "7b", "SunOS/BSD Compatibility Special Files",
   "8", "Maintenance Procedures",
   "8C", "Maintenance Procedures",
   "8c", "Maintenance Procedures",
   "8S", "Maintenance Procedures",
   "8s", "Maintenance Procedures",
   "9", "DDI and DKI",
   "9E", "DDI and DKI Driver Entry Points",
   "9e", "DDI and DKI Driver Entry Points",
   "9F", "DDI and DKI Kernel Functions",
   "9f", "DDI and DKI Kernel Functions",
   "9S", "DDI and DKI Data Structures",
   "9s", "DDI and DKI Data Structures",
   "L", "Local Commands",
 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
   "1", "General Commands",
   "2", "System Calls",
   "3", "Library Functions",
   "4", "Kernel Interfaces",
   "5", "File Formats",
   "6", "Games",
   "7", "Miscellaneous Information",
   "8", "System Manager's Manuals",
   "9", "Kernel Developer's Manuals",
 #else
   // Other OS
   "1", "User Commands ",
   "1C", "User Commands",
   "1G", "User Commands",
   "1S", "User Commands",
   "1V", "User Commands ",
   "2", "System Calls",
   "2V", "System Calls",
   "3", "C Library Functions",
   "3C", "Compatibility Functions",
   "3F", "Fortran Library Routines",
   "3K", "Kernel VM Library Functions",
   "3L", "Lightweight Processes Library",
   "3M", "Mathematical Library",
   "3N", "Network Functions",
   "3R", "RPC Services Library",
   "3S", "Standard I/O Functions",
   "3V", "C Library Functions",
   "3X", "Miscellaneous Library Functions",
   "4", "Devices and Network Interfaces",
   "4F", "Protocol Families",
   "4I", "Devices and Network Interfaces",
   "4M", "Devices and Network Interfaces",
   "4N", "Devices and Network Interfaces",
   "4P", "Protocols",
   "4S", "Devices and Network Interfaces",
   "4V", "Devices and Network Interfaces",
   "5", "File Formats",
   "5V", "File Formats",
   "6", "Games and Demos",
   "7", "Environments, Tables, and Troff Macros",
   "7V", "Environments, Tables, and Troff Macros",
   "8", "Maintenance Commands",
   "8C", "Maintenance Commands",
   "8S", "Maintenance Commands",
   "8V", "Maintenance Commands",
   "L", "Local Commands",
 #endif
   // The defaults
-  NULL, "Misc. Reference Manual Pages",
-  NULL, NULL
+  nullptr, "Misc. Reference Manual Pages",
+  nullptr, nullptr
 };
 
 static const char *section_name(char *c)
 {
   int i = 0;
 
   if (!c) return "";
   while (section_list[i] && qstrcmp(c, section_list[i])) i = i + 2;
   if (section_list[i+1]) return section_list[i+1];
   else return c;
 }
 
 static char *skip_till_newline(char *c)
 {
   int lvl = 0;
 
   while (*c && (*c != '\n' || lvl > 0))
   {
     if (*c == '\\')
     {
       c++;
       if (*c == '}')
         lvl--;
       else if (*c == '{')
         lvl++;
       else if (*c == '\0')
         break;
     }
     c++;
   }
   if (*c) c++;
   if (lvl < 0 && newline_for_fun)
   {
     newline_for_fun = newline_for_fun + lvl;
     if (newline_for_fun < 0) newline_for_fun = 0;
   }
   return c;
 }
 
 static bool s_whileloop = false;
 
 /// Processing the .while request
 static void request_while(char*& c, int j, bool mdoc)
 {
   // ### TODO: .continue
   qCDebug(KIO_MAN_LOG) << "Entering .while";
   c += j;
   char* newline = skip_till_newline(c);
   const char oldchar = *newline;
   *newline = 0;
   // We store the full .while stuff into a QByteArray as if it would be a macro
   const QByteArray macro = c ;
   qCDebug(KIO_MAN_LOG) << "'Macro' of .while" << BYTEARRAY(macro);
   // Prepare for continuing after .while loop end
   *newline = oldchar;
   c = newline;
   // Process -while loop
   const bool oldwhileloop = s_whileloop;
   s_whileloop = true;
   int result = true; // It must be an int due to the call to scan_expression
   break_the_while_loop = false;
   while (result && !break_the_while_loop)
   {
     // Unlike for a normal macro, we have the condition at start, so we do not need to prepend extra bytes
     char* liveloop = qstrdup(macro.data());
     qCDebug(KIO_MAN_LOG) << "Scanning .while condition";
     qCDebug(KIO_MAN_LOG) << "Loop macro " << liveloop;
     char* end_expression = scan_expression(liveloop, &result);
     qCDebug(KIO_MAN_LOG) << "After " << end_expression;
     if (result)
     {
       qCDebug(KIO_MAN_LOG) << "New .while iteration";
       // The condition is true, so call the .while's content
       char* help = end_expression + 1;
       while (*help && (*help == ' '  || *help == '\t'))
         ++help;
       if (! *help)
       {
         // We have a problem, so stop .while
         result = false;
         break;
       }
       if (mdoc)
-        scan_troff_mandoc(help, false, 0);
+        scan_troff_mandoc(help, false, nullptr);
       else
-        scan_troff(help, false, 0);
+        scan_troff(help, false, nullptr);
     }
     delete[] liveloop;
   }
   break_the_while_loop = false;
 
   //
   s_whileloop = oldwhileloop;
   qCDebug(KIO_MAN_LOG) << "Ending .while";
 }
 
 //---------------------------------------------------------------------
 // Processing mixed fonts requests like .BI
 
 static void request_mixed_fonts(char*& c, int j, const char* font1, const char* font2, const bool mode, const bool inFMode)
 {
   c += j;
   if (*c == '\n') c++;
 
   QList args;
   getArguments(c, args);
 
   for (int i = 0; i < args.count(); i++)
   {
     if (mode || inFMode)
     {
       out_html(" ");
       curpos++;
     }
     out_html(set_font((i&1) ? font2 : font1));
-    scan_troff(args[i].data(), 1, NULL);
+    scan_troff(args[i].data(), 1, nullptr);
   }
   out_html(set_font("R"));
   if (mode)
   {
     out_html(" ]");
     curpos++;
   }
   out_html(NEWLINE);
   if (!fillout)
     curpos = 0;
   else
     curpos++;
 }
 
 //---------------------------------------------------------------------
 
 // &%(#@ c programs !!!
 //static int ifelseval=0;
 // If/else can be nested!
 static QStack s_ifelseval;
 
 // Process a (mdoc) request involving quotes
 static char* process_quote(char* c, int j, const char* open, const char* close)
 {
   trans_char(c, '"', '\a');
   c += j;
   if (*c == '\n') c++; // ### TODO: why? Quote requests cannot be empty!
   out_html(open);
-  c = scan_troff_mandoc(c, 1, 0);
+  c = scan_troff_mandoc(c, 1, nullptr);
   out_html(close);
   out_html(NEWLINE);
   if (fillout)
     curpos++;
   else
     curpos = 0;
   return c;
 }
 
 //---------------------------------------------------------------------
 /**
  * Is the char \p ch a puntuaction in sence of mdoc(7)
  */
 
 static bool is_mdoc_punctuation(const char ch)
 {
   if ((ch >= '0' &&  ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
     return false;
   else if (ch == '.' || ch == ',' || ch == ';' || ch == ':' || ch == '(' || ch == ')'
            || ch == '[' || ch == ']')
     return true;
   else
     return false;
 }
 
 //---------------------------------------------------------------------
 /**
  * Can the char \p c be part of an identifier
  * \note For groff, an identifier can consist of nearly all ASCII printable non-white-space characters
  * See info:/groff/Identifiers
  */
 
 static bool is_identifier_char(const char c)
 {
   if (c >= '!' && c <= '[')   // Include digits and upper case
     return true;
   else if (c >= ']' && c <= '~')   // Include lower case
     return true;
   else if (c == '\\')
     return false; // ### TODO: it should be treated as escape instead!
   return false;
 }
 
 //---------------------------------------------------------------------
 
 static QByteArray scan_identifier(char*& c)
 {
   char* h = c; // help pointer
   // ### TODO Groff seems to eat nearly everything as identifier name (info:/groff/Identifiers)
   while (*h && *h != '\a' && *h != '\n' && is_identifier_char(*h))
     ++h;
   const char tempchar = *h;
   *h = 0;
   const QByteArray name = c;
   *h = tempchar;
   if (name.isEmpty())
   {
     qCDebug(KIO_MAN_LOG) << "EXCEPTION: identifier empty!";
   }
   c = h;
   return name;
 }
 
 //---------------------------------------------------------------------
 
 static char *scan_request(char *c)
 {
   // mdoc(7) stuff
   static bool mandoc_synopsis = false; /* True if we are in the synopsis section */
   static bool mandoc_command = false;  /* True if this is mdoc(7) page */
   static int mandoc_bd_options; /* Only copes with non-nested Bd's */
   static int function_argument = 0; // Number of function argument (.Fo, .Fa, .Fc)
 
   int i = 0;
   bool mode = false;
-  char *h = 0;
+  char *h = nullptr;
   char *sl;
   QList args;
 
   while (*c == ' ' || *c == '\t') c++; // Spaces or tabs allowed between control character and request
   if (c[0] == '\n') return c + 1;
   if (c[0] == escapesym)
   {
     /* some pages use .\" .\$1 .\} */
     /* .\$1 is too difficult/stuppid */
     if (c[1] == '$')
     {
       qCDebug(KIO_MAN_LOG) << "Found .\\$";
       c = skip_till_newline(c); // ### TODO
     }
     else
     {
       // the result of the escape expansion must be parsed again
       c++;
       QByteArray cstr;
       c = scan_escape_direct(c, cstr);
       for (; *c && (*c != '\n'); c++) cstr += *c;
       if ( cstr.length() )
         scan_request(cstr.data());
     }
   }
   else
   {
     int nlen = 0;
     QByteArray macroName;
     while (c[nlen] && (c[nlen] != ' ') && (c[nlen] != '\t') && (c[nlen] != '\n') && (c[nlen] != escapesym))
     {
       macroName += c[nlen];
       nlen++;
     }
     int j = nlen;
     while (c[j] == ' ' || c[j] == '\t') j++;
     /* search macro database of self-defined macros */
     QMap::const_iterator it = s_stringDefinitionMap.constFind(macroName);
 
     // ### HACK: e.g. nmap, smb.conf redefine SH, SS to increase the font, etc. for non-TTY output
     // Ignore those to make the HTML result look better
     if ( (macroName != "SH") && (macroName != "SS") &&
          it != s_stringDefinitionMap.constEnd() )
     {
       qCDebug(KIO_MAN_LOG) << "CALLING MACRO: " << BYTEARRAY(macroName);
       const QByteArray oldDollarZero = s_dollarZero; // Previous value of $0
       s_dollarZero = macroName;
 
       c += j;
       getArguments(c, args);
       for (i = 0; i < args.count(); i++)
       {
-        char *h = 0;
+        char *h = nullptr;
 
         if (mandoc_command)
           scan_troff_mandoc(args[i].data(), 1, &h);
         else
           scan_troff(args[i].data(), 1, &h);
 
         args[i] = h;
         delete [] h;
       }
 
       if (!(*it).m_output.isEmpty())
       {
         //qCDebug(KIO_MAN_LOG) << "Macro content is: "<< BYTEARRAY( (*it).m_output );
         const unsigned int length = (*it).m_output.length();
         char* work = new char [length+2];
         work[0] = '\n'; // The macro must start after an end of line to allow a request on first line
         qstrncpy(work + 1, (*it).m_output.data(), length + 1);
         const QList oldArgumentList(s_argumentList);
         s_argumentList.clear();
         for (i = 0; i < args.count(); i++)
           s_argumentList.push_back(args[i]);
 
         const int onff = newline_for_fun;
         if (mandoc_command)
-          scan_troff_mandoc(work + 1, 0, NULL);
+          scan_troff_mandoc(work + 1, 0, nullptr);
         else
-          scan_troff(work + 1, 0, NULL);
+          scan_troff(work + 1, 0, nullptr);
         delete[] work;
         newline_for_fun = onff;
         s_argumentList = oldArgumentList;
       }
       s_dollarZero = oldDollarZero;
       qCDebug(KIO_MAN_LOG) << "ENDING MACRO: " << BYTEARRAY(macroName);
     }
     else
     {
       qCDebug(KIO_MAN_LOG) << "REQUEST: " << BYTEARRAY(macroName);
       switch (RequestNum request = RequestHash::getRequest(macroName, macroName.length()))
       {
         case REQ_ab: // groff(7) "ABort"
         {
           h = c + j;
           while (*h && *h != '\n') h++;
           *h = '\0';
           if (scaninbuff && buffpos)
           {
             buffer[buffpos] = '\0';
             qCDebug(KIO_MAN_LOG) << "ABORT: " << buffer;
           }
           // ### TODO find a way to display it to the user
           qCDebug(KIO_MAN_LOG) << "Aborting: .ab " << (c + j);
-          return 0;
+          return nullptr;
           break;
         }
         case REQ_An: // mdoc(7) "Author Name"
         {
           c += j;
-          c = scan_troff_mandoc(c, 1, 0);
+          c = scan_troff_mandoc(c, 1, nullptr);
           break;
         }
         case REQ_di: // groff(7) "end current DIversion"
         {
           qCDebug(KIO_MAN_LOG) << "Start .di";
           c += j;
           if (*c == '\n')
           {
             ++c;
             break;
           }
           const QByteArray name(scan_identifier(c));
           while (*c && *c != '\n') c++;
           c++;
           h = c;
           while (*c && qstrncmp(c, ".di", 3)) while (*c && *c++ != '\n');
           *c = '\0';
-          char* result = 0;
+          char* result = nullptr;
           scan_troff(h, 0, &result);
           QMap::iterator it = s_stringDefinitionMap.find(name);
           if (it == s_stringDefinitionMap.end())
           {
             StringDefinition def;
             def.m_length = 0;
             def.m_output = result;
             s_stringDefinitionMap.insert(name, def);
           }
           else
           {
             (*it).m_length = 0;
             (*it).m_output = result;
           }
           delete[] result;
           if (*c) *c = '.';
           c = skip_till_newline(c);
           qCDebug(KIO_MAN_LOG) << "end .di";
           break;
         }
         case REQ_ds: // groff(7) "Define String variable"
           mode = true;
         case REQ_as: // groff (7) "Append String variable"
         {
           qCDebug(KIO_MAN_LOG) << "start .ds/.as";
           int oldcurpos = curpos;
           c += j;
           const QByteArray name(scan_identifier(c));
           if (name.isEmpty())
             break;
           // an initial " is removed to allow leading space
           while (*c && isspace(*c)) c++;
           if (*c == '"') c++;
 
           single_escape = true;
           curpos = 0;
-          char* result = 0;
+          char* result = nullptr;
           c = scan_troff(c, 1, &result);
           QMap::iterator it = s_stringDefinitionMap.find(name);
           if (it == s_stringDefinitionMap.end())
           {
             StringDefinition def;
             def.m_length = curpos;
             def.m_output = result;
             s_stringDefinitionMap.insert(name, def);
           }
           else
           {
             if (mode)
             {   // .ds Defining String
               (*it).m_length = curpos;
               (*it).m_output = result;
             }
             else
             {   // .as Appending String
               (*it).m_length += curpos;
               (*it).m_output += result;
             }
           }
           delete[] result;
           single_escape = false;
           curpos = oldcurpos;
           qCDebug(KIO_MAN_LOG) << "end .ds/.as";
           break;
         }
         case REQ_br: // groff(7) "line BReak"
         {
           if (still_dd)
             out_html("
"); // ### VERIFY (does not look like generating good HTML) else out_html("
\n"); curpos = 0; c = c + j; if (c[0] == escapesym) c = scan_escape(c + 1); c = skip_till_newline(c); break; } case REQ_c2: // groff(7) "reset non-break Control character" (2 means non-break) { c = c + j; if (*c != '\n') nobreaksym = *c; else nobreaksym = '\''; c = skip_till_newline(c); break; } case REQ_cc: // groff(7) "reset Control Character" { c = c + j; if (*c != '\n') controlsym = *c; else controlsym = '.'; c = skip_till_newline(c); break; } case REQ_ce: // groff (7) "CEnter" { c = c + j; if (*c == '\n') i = 1; else { i = 0; while ('0' <= *c && *c <= '9') { i = i * 10 + *c - '0'; c++; } } c = skip_till_newline(c); /* center next i lines */ if (i > 0) { out_html("
\n"); while (i && *c) { - char *line = NULL; + char *line = nullptr; c = scan_troff(c, 1, &line); if (line && qstrncmp(line, "
", 4)) { out_html(line); out_html("
\n"); delete [] line; // ### FIXME: memory leak! i--; } } out_html("
\n"); curpos = 0; } break; } case REQ_ec: // groff(7) "reset Escape Character" { c = c + j; if (*c != '\n') escapesym = *c; else escapesym = '\\'; break; c = skip_till_newline(c); } case REQ_eo: // groff(7) "turn Escape character Off" { escapesym = '\0'; c = skip_till_newline(c); break; } case REQ_ex: // groff(7) "EXit" { - return 0; + return nullptr; break; } case REQ_fc: // groff(7) "set Field and pad Character" { c = c + j; if (*c == '\n') fieldsym = padsym = '\0'; else { fieldsym = c[0]; padsym = c[1]; } c = skip_till_newline(c); break; } case REQ_fi: // groff(7) "FIll" { if (!fillout) { out_html(set_font("R")); out_html(change_to_size('0')); out_html("
\n"); } curpos = 0; fillout = 1; c = skip_till_newline(c); break; } case REQ_ft: // groff(7) "FonT" { c += j; h = skip_till_newline(c); const char oldChar = *h; *h = 0; const QByteArray name = c; // ### TODO: name might contain a variable if (name.isEmpty()) out_html(set_font("P")); // Previous font else out_html(set_font(name)); *h = oldChar; c = h; break; } case REQ_el: // groff(7) "ELse" { int ifelseval = s_ifelseval.pop(); /* .el anything : else part of if else */ if (ifelseval) { c = c + j; c[-1] = '\n'; - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); } else c = skip_till_newline(c + j); break; } case REQ_ie: // groff(7) "If with Else" /* .ie c anything : then part of if else */ // fallthrough case REQ_if: // groff(7) "IF" { /* .if c anything * .if !c anything * .if N anything * .if !N anything * .if 'string1'string2' anything * .if !'string1'string2' anything */ c = c + j; c = scan_expression(c, &i); if (request == REQ_ie) { int ifelseval = !i; s_ifelseval.push(ifelseval); } if (i) { *c = '\n'; c++; - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); } else c = skip_till_newline(c); break; } case REQ_ig: // groff(7) "IGnore" { const char *endwith = "..\n"; i = 3; c = c + j; if (*c != '\n' && *c != '\\') { /* Not newline or comment */ endwith = c - 1; i = 1; c[-1] = '.'; while (*c && *c != '\n') c++, i++; } c++; while (*c && qstrncmp(c, endwith, i)) while (*c++ != '\n'); while (*c && *c++ != '\n'); break; } case REQ_nf: // groff(7) "No Filling" { if (fillout) { out_html(set_font("R")); out_html(change_to_size('0')); out_html("
\n");
           }
           curpos = 0;
           fillout = 0;
           c = skip_till_newline(c);
           break;
         }
         case REQ_ps: // groff(7) "previous Point Size"
         {
           c += j;
           getArguments(c, args);
           if ( args.count() == 0 )
             out_html(change_to_size('0'));
           else
           {
             char *h = args[0].data();
             j = 0;
             i = 0;
             if (*h == '-')
             {
               j = -1;
               h++;
             }
             else if (*h == '+')
               j = 1;
             h++;
             scan_expression(h, &i);
             if (!j)
             {
               j = 1;
               if (i > 5) i = i - 10;
             }
             out_html(change_to_size(i*j));
           }
           break;
         }
         case REQ_sp: // groff(7) "SKip one line"
         {
           c += j;
           if (fillout)
             out_html("

"); else out_html(NEWLINE); curpos = 0; c = skip_till_newline(c); break; } case REQ_so: // groff(7) "Include SOurce file" { char *buf; - char *name = NULL; + char *name = nullptr; curpos = 0; c = c + j; if (*c == '/') h = c; else { h = c - 3; h[0] = '.'; h[1] = '.'; h[2] = '/'; } while (*c != '\n') c++; *c = '\0'; scan_troff(h, 1, &name); if (name[3] == '/') h = name + 3; else h = name; /* this works alright, except for section 3 */ buf = read_man_page(h); if (!buf) { qCDebug(KIO_MAN_LOG) << "Unable to open or read file: .so " << (h); out_html("
" "man2html: unable to open or read file.\n"); out_html(h); out_html("
\n"); } else - scan_troff(buf + 1, 0, NULL); + scan_troff(buf + 1, 0, nullptr); delete [] buf; delete [] name; *c++ = '\n'; break; } case REQ_ta: // gorff(7) "set TAbulators" { c = c + j; j = 0; while (*c != '\n') { sl = scan_expression(c, &tabstops[j]); if (j > 0 && (*c == '-' || *c == '+')) tabstops[j] += tabstops[j-1]; c = sl; while (*c == ' ' || *c == '\t') c++; j++; } maxtstop = j; curpos = 0; break; } case REQ_ti: // groff(7) "Temporary Indent" { /*while (itemdepth || dl_set[itemdepth]) { out_html("\n"); if (dl_set[itemdepth]) dl_set[itemdepth]=0; else itemdepth--; }*/ out_html("
\n"); c = c + j; c = scan_expression(c, &j); for (i = 0; i < j; i++) out_html(" "); curpos = j; c = skip_till_newline(c); break; } case REQ_tm: // groff(7) "TerMinal" ### TODO: what are useful uses for it { c += j; getArguments(c, args); if ( args.count() ) qCDebug(KIO_MAN_LOG) << ".tm " << args[0]; break; } case REQ_B: // man(7) "Bold" mode = 1; case REQ_I: // man(7) "Italic" { /* parse one line in a certain font */ c += j; getArguments(c, args); out_html(set_font(mode ? "B" : "I")); for (int i = 0; i < args.count(); i++) { - scan_troff(args[i].data(), 1, 0); + scan_troff(args[i].data(), 1, nullptr); out_html(" "); } out_html(set_font("R")); if (fillout) curpos++; else { out_html(NEWLINE); curpos = 0; } break; } case REQ_Fd: // mdoc(7) "Function Definition" { // Normal text must be printed in bold, punctuation in regular font c += j; if (*c == '\n') c++; getArguments(c, args); for (i = 0; i < args.count(); i++) { // ### FIXME In theory, only a single punctuation character is recognized as punctuation if ( is_mdoc_punctuation(args[i][0]) ) out_html(set_font("R")); else out_html(set_font("B")); - scan_troff(args[i].data(), 1, NULL); + scan_troff(args[i].data(), 1, nullptr); out_html(" "); } // In the mdoc synopsis, there are automatical line breaks (### TODO: before or after?) if (mandoc_synopsis) out_html("
"); out_html(set_font("R")); out_html(NEWLINE); if (!fillout) curpos = 0; else curpos++; break; } case REQ_Fn: // mdoc(7) for "Function calls" { // brackets and commas have to be inserted automatically c += j; if (*c == '\n') c++; getArguments(c, args); if ( args.count() ) { for (i = 0; i < args.count(); i++) { if (i) out_html(set_font("I")); else out_html(set_font("B")); - scan_troff(args[i].data(), 1, NULL); + scan_troff(args[i].data(), 1, nullptr); out_html(set_font("R")); if (i == 0) { out_html(" ("); } else if (i < args.count() - 1) out_html(", "); } out_html(")"); } out_html(set_font("R")); if (mandoc_synopsis) out_html("
"); out_html(NEWLINE); if (!fillout) curpos = 0; else curpos++; break; } case REQ_Fo: // mdoc(7) "Function definition Opening" { char* font[2] = {(char*)"B", (char*)"R" }; c += j; if (*c == '\n') c++; char *eol = strchr(c, '\n'); char *semicolon = strchr(c, ';'); - if ((semicolon != 0) && (semicolon < eol)) *semicolon = ' '; + if ((semicolon != nullptr) && (semicolon < eol)) *semicolon = ' '; getArguments(c, args); // Normally a .Fo has only one parameter for (i = 0; i < args.count(); i++) { out_html(set_font(font[i&1])); - scan_troff(args[i].data(), 1, NULL); + scan_troff(args[i].data(), 1, nullptr); if (i == 0) { out_html(" ("); } // ### TODO What should happen if there is more than one argument // else if (i 0 out_html(set_font("R")); out_html(NEWLINE); if (!fillout) curpos = 0; else curpos++; break; } case REQ_Fc:// mdoc(7) "Function definition Close" { // .Fc has no parameter c += j; c = skip_till_newline(c); char* font[2] = {(char*)"B", (char*)"R" }; out_html(set_font(font[i&1])); out_html(")"); out_html(set_font("R")); if (mandoc_synopsis) out_html("
"); out_html(NEWLINE); if (!fillout) curpos = 0; else curpos++; function_argument = 0; // Reset the count variable break; } case REQ_Fa: // mdoc(7) "Function definition argument" { char* font[2] = {(char*)"B", (char*)"R" }; c += j; if (*c == '\n') c++; getArguments(c, args); out_html(set_font(font[i&1])); // function_argument==0 means that we had no .Fo before, e.g. in mdoc.samples(7) if (function_argument > 1) { out_html(", "); curpos += 2; function_argument++; } else if (function_argument == 1) { // We are only at the first parameter function_argument++; } for (i = 0; i < args.count(); i++) - scan_troff(args[i].data(), 1, NULL); + scan_troff(args[i].data(), 1, nullptr); out_html(set_font("R")); if (!fillout) curpos = 0; else curpos++; break; } case REQ_OP: /* groff manpages use this construction */ { /* .OP a b : [ a b ] */ out_html(set_font("R")); out_html("["); curpos++; request_mixed_fonts(c, j, "B", "I", true, false); break; } case REQ_Ft: //perhaps "Function return type" { request_mixed_fonts(c, j, "B", "I", false, true); break; } case REQ_BR: { request_mixed_fonts(c, j, "B", "R", false, false); break; } case REQ_BI: { request_mixed_fonts(c, j, "B", "I", false, false); break; } case REQ_IB: { request_mixed_fonts(c, j, "I", "B", false, false); break; } case REQ_IR: { request_mixed_fonts(c, j, "I", "R", false, false); break; } case REQ_RB: { request_mixed_fonts(c, j, "R", "B", false, false); break; } case REQ_RI: { request_mixed_fonts(c, j, "R", "I", false, false); break; } case REQ_DT: // man(7) "Default Tabulators" { for (j = 0;j < 20; j++) tabstops[j] = (j + 1) * 8; maxtstop = 20; c = skip_till_newline(c); break; } case REQ_IP: // man(7) "Ident Paragraph" { c += j; getArguments(c, args); if (!dl_set[itemdepth]) { out_html("
\n"); dl_set[itemdepth] = 1; } out_html("
"); if ( args.count() ) - scan_troff(args[0].data(), 1, NULL); + scan_troff(args[0].data(), 1, nullptr); out_html("
\n
"); listItemStack.push("DD"); curpos = 0; break; } case REQ_TP: // man(7) "hanging Tag Paragraph" { if (!dl_set[itemdepth]) { out_html("
\n"); dl_set[itemdepth] = 1; } out_html(set_font("R")); out_html("
"); c = skip_till_newline(c); /* somewhere a definition ends with '.TP' */ if (!*c) still_dd = true; else { // HACK for proc(5) while (c[0] == '.' && c[1] == '\\' && c[2] == '\"') { // We have a comment, so skip the line c = skip_till_newline(c); } - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); out_html("
"); listItemStack.push("DD"); } curpos = 0; break; } case REQ_IX: // Indexing term (printed on standard error) { c = skip_till_newline(c); // ignore break; } case REQ_P: // man(7) "Paragraph" case REQ_LP:// man(7) "Paragraph" case REQ_PP:// man(7) "Paragraph; reset Prevailing indent" { if (dl_set[itemdepth]) { out_html("
\n"); dl_set[itemdepth] = 0; } else if (fillout) out_html("
"); if (fillout) out_html("
\n"); else { out_html(NEWLINE); } curpos = 0; c = skip_till_newline(c); break; } case REQ_HP: // man(7) "Hanging indent Paragraph" { if (!dl_set[itemdepth]) { out_html("
"); dl_set[itemdepth] = 1; } out_html("
\n"); still_dd = true; c = skip_till_newline(c); curpos = 0; break; } case REQ_PD: // man(7) "Paragraph Distance" { c = skip_till_newline(c); break; } case REQ_Rs: // mdoc(7) "Relative margin Start" case REQ_RS: // man(7) "Relative margin Start" { c += j; getArguments(c, args); j = 1; if (args.count() > 0) scan_expression(args[0].data(), &j); if (j >= 0) { itemdepth++; dl_set[itemdepth] = 0; out_html("
"); listItemStack.push("DD"); curpos = 0; } break; } case REQ_Re: // mdoc(7) "Relative margin End" case REQ_RE: // man(7) "Relative margin End" { if (itemdepth > 0) { if (dl_set[itemdepth]) out_html("
"); out_html("
\n"); itemdepth--; } c = skip_till_newline(c); curpos = 0; break; } case REQ_SB: // man(7) "Small; Bold" { out_html(set_font("B")); out_html(""); - c = scan_troff(c + j, 1, NULL); + c = scan_troff(c + j, 1, nullptr); out_html(""); out_html(set_font("R")); break; } case REQ_SM: // man(7) "SMall" { c = c + j; if (*c == '\n') c++; out_html(""); - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); out_html(""); break; } case REQ_Ss: // mdoc(7) "Sub Section" mandoc_command = 1; case REQ_SS: // mdoc(7) "Sub Section" mode = true; case REQ_Sh: // mdoc(7) "Sub Header" /* hack for fallthru from above */ mandoc_command = !mode || mandoc_command; case REQ_SH: // man(7) "Sub Header" { c = c + j; if (*c == '\n') c++; while (itemdepth || dl_set[itemdepth]) { out_html("
\n"); if (dl_set[itemdepth]) dl_set[itemdepth] = 0; else if (itemdepth > 0) itemdepth--; } out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout = 1; out_html("
"); } trans_char(c, '"', '\a'); if (section) { out_html("\n"); section = 0; } if (mode) out_html("\n

"); else out_html("\n

"); mandoc_synopsis = qstrncmp(c, "SYNOPSIS", 8) == 0; - c = mandoc_command ? scan_troff_mandoc(c, 1, NULL) : scan_troff(c, 1, NULL); + c = mandoc_command ? scan_troff_mandoc(c, 1, nullptr) : scan_troff(c, 1, nullptr); if (mode) out_html("

\n"); else out_html("\n"); out_html("
\n"); section = 1; curpos = 0; break; } case REQ_Sx: // mdoc(7) { // reference to a section header out_html(set_font("B")); trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_TS: // Table Start tbl(1) { c = scan_table(c); break; } case REQ_Dt: /* mdoc(7) */ mandoc_command = true; case REQ_TH: // man(7) "Title Header" { if (!output_possible) { c += j; getArguments(c, args); output_possible = true; out_html(DOCTYPE"\n\n"); out_html("\n"); out_html(""); if ( args.count() ) { // work around the problem that in a title no HTML tags are allowed // but args[0] can have formatting escapes, e.g. to switch a font // which results in a HTML tag added to the output - char *result = 0; + char *result = nullptr; scan_troff(args[0].data(), 0, &result); char *p = result; QByteArray title; while ( *p ) { if ( *p == '<' ) // tag begin -> skip whole tag { for (p++; *p && (*p != '>'); p++) ; if ( *p ) p++; } if ( *p ) title += *p++; } ignore_links = true; title += '\n'; // needed so that out_html flushes buffer and ignore_links works out_html(title); ignore_links = false; delete [] result; } out_html(" Manpage\n"); // KDE defaults. out_html("\n"); // Output our custom stylesheet. out_html("\n"); // Some elements need background images, but this // could not be included in the stylesheet, // include it now. out_html("\n\n" ); out_html("\n"); out_html("\n\n"); out_html("\n\n"); out_html("
\n"); out_html("
\n"); out_html("\"top-kde\" "); if ( args.count() ) - scan_troff(args[0].data(), 0, NULL); + scan_troff(args[0].data(), 0, nullptr); out_html(" - KDE Man Page Viewer"); out_html("
\n"); out_html("
\n"); out_html("

"); if ( args.count() ) - scan_troff(args[0].data(), 0, NULL); + scan_troff(args[0].data(), 0, nullptr); out_html("

\n"); if (args.count() > 1) { out_html("Section: "); if ( !mandoc_command && (args.count() > 4) ) - scan_troff(args[4].data(), 0, NULL); + scan_troff(args[4].data(), 0, nullptr); else out_html(section_name(args[1].data())); out_html(" ("); - scan_troff(args[1].data(), 0, NULL); + scan_troff(args[1].data(), 0, nullptr); out_html(")\n"); } else { out_html("Section not specified"); } } else { qCWarning(KIO_MAN_LOG) << ".TH found but output not possible" ; c = skip_till_newline(c); } curpos = 0; break; } case REQ_TX: // mdoc(7) { c += j; getArguments(c, args); out_html(set_font("I")); const char *c2 = lookup_abbrev(args[0]); curpos += qstrlen(c2); out_html(c2); out_html(set_font("R")); if (args.count() > 1) out_html(args[1]); break; } case REQ_rm: // groff(7) "ReMove" /* .rm xx : Remove request, macro or string */ mode = true; case REQ_rn: // groff(7) "ReName" /* .rn xx yy : Rename request, macro or string xx to yy */ { qCDebug(KIO_MAN_LOG) << "start .rm/.rn"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty origin string to remove/rename"; break; } QByteArray name2; if (!mode) { while (*c && isspace(*c) && *c != '\n') ++c; name2 = scan_identifier(c); if (name2.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty destination string to rename"; break; } } c = skip_till_newline(c); QMap::iterator it = s_stringDefinitionMap.find(name); if (it == s_stringDefinitionMap.end()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find string to rename or remove: " << BYTEARRAY(name); } else { if (mode) { // .rm ReMove s_stringDefinitionMap.remove(name); // ### QT4: removeAll } else { // .rn ReName StringDefinition def = (*it); s_stringDefinitionMap.remove(name); // ### QT4: removeAll s_stringDefinitionMap.insert(name2, def); } } qCDebug(KIO_MAN_LOG) << "end .rm/.rn"; break; } case REQ_nx: case REQ_in: // groff(7) "INdent" { /* .in +-N : Indent */ c = skip_till_newline(c); break; } case REQ_nr: // groff(7) "Number Register" { qCDebug(KIO_MAN_LOG) << "start .nr"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty name for register variable"; break; } while (*c && (*c == ' ' || *c == '\t')) c++; int sign = 0; if (*c && (*c == '+' || *c == '-')) { if (*c == '+') sign = 1; else if (*c == '-') sign = -1; } int value = 0; int increment = 0; c = scan_expression(c, &value); if (*c && *c != '\n') { while (*c && (*c == ' ' || *c == '\t')) c++; c = scan_expression(c, &increment); } c = skip_till_newline(c); QMap ::iterator it = s_numberDefinitionMap.find(name); if (it == s_numberDefinitionMap.end()) { if (sign < 1) value = -value; NumberDefinition def(value, increment); s_numberDefinitionMap.insert(name, def); } else { if (sign > 0) (*it).m_value += value; else if (sign < 0) (*it).m_value += - value; else (*it).m_value = value; (*it).m_increment = increment; } qCDebug(KIO_MAN_LOG) << "end .nr"; break; } case REQ_am: // groff(7) "Append Macro" /* .am xx yy : append to a macro. */ /* define or handle as .ig yy */ mode = true; // fallthrough case REQ_de: // groff(7) "DEfine macro" case REQ_de1: // groff(7) "DEfine macro" { /* .de xx yy : define or redefine macro xx; end at .yy (..) */ /* define or handle as .ig yy */ qCDebug(KIO_MAN_LOG) << "Start .am/.de"; c += j; getArguments(c, args); if ( args.count() == 0 ) break; const QByteArray name(args[0]); QByteArray endmacro; if (args.count() == 1) endmacro = ".."; else endmacro = "." + args[1]; // krazy:exclude=doublequote_chars sl = c; while (*c && qstrncmp(c, endmacro, endmacro.length())) c = skip_till_newline(c); QByteArray macro; while (sl != c) { if (sl[0] == '\\' && sl[1] == '\\') { macro += '\\'; sl++; } else macro += *sl; sl++; } QMap::iterator it = s_stringDefinitionMap.find(name); if (it == s_stringDefinitionMap.end()) { StringDefinition def; def.m_length = 0; def.m_output = macro; s_stringDefinitionMap.insert(name, def); } else if (mode) { // .am Append Macro (*it).m_length = 0; // It could be formerly a string if (!(*it).m_output.endsWith('\n')) (*it).m_output += '\n'; (*it).m_output += macro; } else { // .de DEfine macro (*it).m_length = 0; // It could be formerly a string (*it).m_output = macro; } c = skip_till_newline(c); qCDebug(KIO_MAN_LOG) << "End .am/.de"; break; } case REQ_Bl: // mdoc(7) "Begin List" { char list_options[NULL_TERMINATED(MED_STR_MAX)]; char *nl = strchr(c, '\n'); c = c + j; if (dl_set[itemdepth]) { /* These things can nest. */ itemdepth++; } if (nl) { /* Parse list options */ strlimitcpy(list_options, c, nl - c, MED_STR_MAX); } if (strstr(list_options, "-bullet")) { /* HTML Unnumbered List */ dl_set[itemdepth] = BL_BULLET_LIST; out_html("
    \n"); } else if (strstr(list_options, "-enum")) { /* HTML Ordered List */ dl_set[itemdepth] = BL_ENUM_LIST; out_html("
      \n"); } else { /* HTML Descriptive List */ dl_set[itemdepth] = BL_DESC_LIST; out_html("
      \n"); } curpos = 0; c = skip_till_newline(c); break; } case REQ_El: // mdoc(7) "End List" { checkListStack(); c = c + j; if (dl_set[itemdepth] & BL_DESC_LIST) out_html("
      \n"); else if (dl_set[itemdepth] & BL_BULLET_LIST) out_html("
\n"); else if (dl_set[itemdepth] & BL_ENUM_LIST) out_html("\n"); dl_set[itemdepth] = 0; if (itemdepth > 0) itemdepth--; if ( !fillout ) out_html(NEWLINE); curpos = 0; c = skip_till_newline(c); break; } case REQ_It: // mdoc(7) "list ITem" { checkListStack(); c = c + j; //if (qstrncmp(c, "Xo", 2) == 0 && isspace(*(c + 2))) //c = skip_till_newline(c); if (dl_set[itemdepth] & BL_DESC_LIST) { out_html("
"); out_html(set_font("B")); if (*c == '\n') { /* Don't allow embedded comms after a newline */ c++; - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); } else { /* Do allow embedded comms on the same line. */ - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); } out_html(set_font("R")); out_html("
"); out_html(NEWLINE); out_html("
"); listItemStack.push("DD"); } else if (dl_set[itemdepth] & (BL_BULLET_LIST | BL_ENUM_LIST)) { out_html("
  • "); listItemStack.push("LI"); - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(NEWLINE); } if (fillout) curpos++; else curpos = 0; break; } case REQ_Bk: /* mdoc(7) */ case REQ_Ek: /* mdoc(7) */ case REQ_Dd: /* mdoc(7) */ case REQ_Os: // mdoc(7) "Operating System" case REQ_Sm: // mdoc(7) space mode c = skip_till_newline(c); // TODO break; case REQ_Bt: // mdoc(7) "Beta Test" { //trans_char(c, '"', '\a'); //c = c + j; out_html(" is currently in beta test."); if (fillout) curpos++; else curpos = 0; break; } case REQ_At: /* mdoc(7) */ case REQ_Fx: /* mdoc(7) */ case REQ_Nx: /* mdoc(7) */ case REQ_Ox: /* mdoc(7) */ case REQ_Bx: /* mdoc(7) */ case REQ_Ux: /* mdoc(7) */ case REQ_Dx: /* mdoc(7) */ { bool parsable = true; trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; if (request == REQ_At) { out_html("AT&T UNIX "); parsable = false; } else if (request == REQ_Fx) { out_html("FreeBSD "); parsable = false; } else if (request == REQ_Nx) out_html("NetBSD "); else if (request == REQ_Ox) out_html("OpenBSD "); else if (request == REQ_Bx) out_html("BSD "); else if (request == REQ_Ux) out_html("UNIX "); else if (request == REQ_Dx) out_html("DragonFly "); if (parsable) - c = scan_troff_mandoc(c, 1, 0); + c = scan_troff_mandoc(c, 1, nullptr); else - c = scan_troff(c, 1, 0); + c = scan_troff(c, 1, nullptr); if (fillout) curpos++; else curpos = 0; break; } case REQ_Dl: /* mdoc(7) */ { c = c + j; out_html(NEWLINE); out_html("
    "); if (*c == '\n') c++; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html("
    "); if (fillout) curpos++; else curpos = 0; break; } case REQ_Bd: /* mdoc(7) */ { /* Seems like a kind of example/literal mode */ char bd_options[NULL_TERMINATED(MED_STR_MAX)]; char *nl = strchr(c, '\n'); c = c + j; if (nl) strlimitcpy(bd_options, c, nl - c, MED_STR_MAX); out_html(NEWLINE); mandoc_bd_options = 0; /* Remember options for terminating Bl */ if (strstr(bd_options, "-offset indent")) { mandoc_bd_options |= BD_INDENT; out_html("
    \n"); } if (strstr(bd_options, "-literal") || strstr(bd_options, "-unfilled")) { if (fillout) { mandoc_bd_options |= BD_LITERAL; out_html(set_font("R")); out_html(change_to_size('0')); out_html("
    \n");
                 }
                 curpos = 0;
                 fillout = 0;
               }
               c = skip_till_newline(c);
               break;
             }
             case REQ_Ed:    /* mdoc(7) */
             {
               if (mandoc_bd_options & BD_LITERAL)
               {
                 if (!fillout)
                 {
                   out_html(set_font("R"));
                   out_html(change_to_size('0'));
                   out_html("
    \n"); } } if (mandoc_bd_options & BD_INDENT) out_html("
    \n"); curpos = 0; fillout = 1; c = skip_till_newline(c); break; } case REQ_Be: /* mdoc(7) */ { c = c + j; if (fillout) out_html("

    "); else { out_html(NEWLINE); } curpos = 0; c = skip_till_newline(c); break; } case REQ_Xr: /* mdoc(7) */ // ### FIXME: it should issue a directly { /* Translate xyz 1 to xyz(1) * Allow for multiple spaces. Allow the section to be missing. */ char buff[NULL_TERMINATED(MED_STR_MAX)]; char *bufptr; trans_char(c, '"', '\a'); bufptr = buff; c = c + j; if (*c == '\n') c++; /* Skip spaces */ while (isspace(*c) && *c != '\n') c++; while (isalnum(*c) || *c == '.' || *c == ':' || *c == '_' || *c == '-') { /* Copy the xyz part */ *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; c++; } while (isspace(*c) && *c != '\n') c++; /* Skip spaces */ if (isdigit(*c)) { /* Convert the number if there is one */ *bufptr = '('; bufptr++; if (bufptr < buff + MED_STR_MAX) { while (isalnum(*c)) { *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; c++; } if (bufptr < buff + MED_STR_MAX) { *bufptr = ')'; bufptr++; } } } while (*c != '\n') { /* Copy the remainder */ if (!isspace(*c)) { *bufptr = *c; bufptr++; if (bufptr >= buff + MED_STR_MAX) break; } c++; } *bufptr = '\n'; bufptr[1] = 0; - scan_troff_mandoc(buff, 1, NULL); + scan_troff_mandoc(buff, 1, nullptr); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Fl: // mdoc(7) "FLags" { //trans_char(c, '"', '\a'); c += j; QList argPointers; getArguments(c, args, &argPointers); out_html(set_font("B")); out_html("-"); if ( args.count() == 0 ) { /*out_html("-");*/ // stdin or stdout } else { if ( argPointers.count() ) - scan_troff_mandoc(argPointers[0], 1, NULL); + scan_troff_mandoc(argPointers[0], 1, nullptr); /* for (i = 0; i < args.count(); ++i) { if (ispunct(args[i][0]) && args[i][0] != '-') { scan_troff_mandoc(argPointers[i], 1, NULL); } else { if (i > 0) out_html(" "); // Put a space between flags out_html("-"); scan_troff_mandoc(argPointers[i], 1, NULL); } } */ } out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Pa: /* mdoc(7) */ case REQ_Pf: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Pp: /* mdoc(7) */ { if (fillout) out_html("

    \n"); else { out_html(NEWLINE); } curpos = 0; c = skip_till_newline(c); break; } case REQ_Aq: // mdoc(7) "Angle bracket Quote" c = process_quote(c, j, "<", ">"); break; case REQ_Bq: // mdoc(7) "Bracket Quote" c = process_quote(c, j, "[", "]"); break; case REQ_Dq: // mdoc(7) "Double Quote" c = process_quote(c, j, "“", "”"); break; case REQ_Pq: // mdoc(7) "Parenthese Quote" c = process_quote(c, j, "(", ")"); break; case REQ_Qq: // mdoc(7) "straight double Quote" c = process_quote(c, j, """, """); break; case REQ_Sq: // mdoc(7) "Single Quote" c = process_quote(c, j, "‘", "’"); break; case REQ_Op: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; out_html(set_font("R")); out_html("["); - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(set_font("R")); out_html("]"); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Oo: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; out_html(set_font("R")); out_html("["); - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); if (fillout) curpos++; else curpos = 0; break; } case REQ_Oc: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(set_font("R")); out_html("]"); if (fillout) curpos++; else curpos = 0; break; } case REQ_Ql: /* mdoc(7) */ { /* Single quote first word in the line */ char *sp; trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; sp = c; do { /* Find first whitespace after the * first word that isn't a mandoc macro */ while (*sp && isspace(*sp)) sp++; while (*sp && !isspace(*sp)) sp++; } while (*sp && isupper(*(sp - 2)) && islower(*(sp - 1))); /* Use a newline to mark the end of text to * be quoted */ if (*sp) *sp = '\n'; out_html("`"); /* Quote the text */ - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html("'"); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Ar: /* mdoc(7) */ { /* parse one line in italics */ out_html(set_font("I")); c += j; QList argPointers; getArguments(c, args, &argPointers); if ( args.count() == 0 ) { // An empty Ar means "file ..." out_html("file ..."); } else { if ( argPointers.count() ) - c = scan_troff_mandoc(argPointers[0], 1, NULL); + c = scan_troff_mandoc(argPointers[0], 1, nullptr); } out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Em: /* mdoc(7) */ { out_html(""); trans_char(c, '"', '\a'); c += j; if (*c == '\n') c++; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(""); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Ad: /* mdoc(7) */ case REQ_Va: /* mdoc(7) */ case REQ_Xo: /* mdoc(7) */ case REQ_Xc: /* mdoc(7) */ { /* parse one line in italics */ out_html(set_font("I")); trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Nd: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; out_html(" - "); - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Nm: // mdoc(7) "Name Macro" { c += j; QList argPointers; getArguments(c, args, &argPointers); if ( mandoc_name.isEmpty() && args.count() ) mandoc_name = args[0]; out_html(set_font("B")); // only show name if // .Nm (first not-null-length defined name) // .Nm name // do not show // .Nm "" if ( args.count() == 0 ) - scan_troff(mandoc_name.data(), 0, 0); + scan_troff(mandoc_name.data(), 0, nullptr); else { if ( argPointers.count() ) - c = scan_troff_mandoc(argPointers[0], 1, 0); + c = scan_troff_mandoc(argPointers[0], 1, nullptr); } out_html(set_font("R")); #if 0 if (mandoc_synopsis && mandoc_name_count) { /* Break lines only in the Synopsis. * The Synopsis section seems to be treated * as a special case - Bummer! */ out_html("
    "); } else if (!mandoc_name_count) { const char *nextbreak = strchr(c, '\n'); const char *nextspace = strchr(c, ' '); if (nextspace < nextbreak) nextbreak = nextspace; if (nextbreak) { /* Remember the name for later. */ strlimitcpy(mandoc_name, c, nextbreak - c, SMALL_STR_MAX); } } mandoc_name_count++; out_html(set_font("B")); // ### FIXME: fill_words must be used while (*c == ' ' || *c == '\t') c++; if ((tolower(*c) >= 'a' && tolower(*c) <= 'z') || (*c >= '0' && *c <= '9')) { // alphanumeric argument c = scan_troff_mandoc(c, 1, NULL); out_html(set_font("R")); out_html(NEWLINE); } else { /* If Nm has no argument, use one from an earlier * Nm command that did have one. Hope there aren't * too many commands that do this. */ out_html(mandoc_name); out_html(set_font("R")); } #endif if (fillout) curpos++; else curpos = 0; break; } case REQ_Cd: /* mdoc(7) */ case REQ_Cm: /* mdoc(7) */ case REQ_Ic: /* mdoc(7) */ case REQ_Ms: /* mdoc(7) */ case REQ_Or: /* mdoc(7) */ case REQ_Sy: /* mdoc(7) */ { /* parse one line in bold */ out_html(set_font("B")); trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_Ta: /* mdoc(7) */ { // ### FIXME: this is a simplification // for a list item element in a ".Bl -tag -width indent" type list // man:mdoc says: "indent == Six constant width spaces" out_html("      "); c = c + j; if (*c == '\n') c++; break; } // ### FIXME: punctuation is handled badly! case REQ_Dv: /* mdoc(7) */ case REQ_Ev: /* mdoc(7) */ case REQ_Fr: /* mdoc(7) */ case REQ_Li: /* mdoc(7) */ case REQ_No: /* mdoc(7) */ case REQ_Ns: /* mdoc(7) */ case REQ_Tn: /* mdoc(7) */ case REQ_nN: /* mdoc(7) */ { trans_char(c, '"', '\a'); c = c + j; if (*c == '\n') c++; out_html(set_font("B")); - c = scan_troff_mandoc(c, 1, NULL); + c = scan_troff_mandoc(c, 1, nullptr); out_html(set_font("R")); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; break; } case REQ_perc_A: /* mdoc(7) biblio stuff */ case REQ_perc_D: case REQ_perc_N: case REQ_perc_O: case REQ_perc_P: case REQ_perc_Q: case REQ_perc_V: { c = c + j; if (*c == '\n') c++; - c = scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ + c = scan_troff(c, 1, nullptr); /* Don't allow embedded mandoc coms */ if (fillout) curpos++; else curpos = 0; break; } case REQ_perc_B: case REQ_perc_J: case REQ_perc_R: case REQ_perc_T: { c = c + j; out_html(set_font("I")); if (*c == '\n') c++; - c = scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ + c = scan_troff(c, 1, nullptr); /* Don't allow embedded mandoc coms */ out_html(set_font("R")); if (fillout) curpos++; else curpos = 0; break; } case REQ_URL: // man(7) ".URL url link trailer" { c += j; getArguments(c, args); ignore_links = true; out_html("
    0 ) - scan_troff(args[0].data(), 0, 0); + scan_troff(args[0].data(), 0, nullptr); out_html("\">"); if ( args.count() > 1 ) - scan_troff(args[1].data(), 0, 0); + scan_troff(args[1].data(), 0, nullptr); out_html("\n"); // trailing newline important to make ignore_links work ignore_links = false; if ( args.count() > 2 ) - scan_troff(args[2].data(), 1, NULL); + scan_troff(args[2].data(), 1, nullptr); break; } case REQ_tr: // translate TODO { c = skip_till_newline(c); break; } case REQ_nroff: // groff(7) "NROFF mode" mode = true; case REQ_troff: // groff(7) "TROFF mode" { s_nroff = mode; c += j; c = skip_till_newline(c); break; } case REQ_als: // groff(7) "ALias String" { /* * Note an alias is supposed to be something like a hard link * However to make it simplier, we only copy the string. */ // Be careful: unlike .rn, the destination is first, origin is second qCDebug(KIO_MAN_LOG) << "start .als"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty destination string to alias"; break; } while (*c && isspace(*c) && *c != '\n') ++c; const QByteArray name2(scan_identifier(c)); if (name2.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty origin string to alias"; break; } qCDebug(KIO_MAN_LOG) << "Alias " << BYTEARRAY(name2) << " to " << BYTEARRAY(name); c = skip_till_newline(c); if (name == name2) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: same origin and destination string to alias: " << BYTEARRAY(name); break; } // Second parameter is origin (unlike in .rn) QMap::iterator it = s_stringDefinitionMap.find(name2); if (it == s_stringDefinitionMap.end()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find string to make alias of " << BYTEARRAY(name2); } else { StringDefinition def = (*it); s_stringDefinitionMap.insert(name, def); } qCDebug(KIO_MAN_LOG) << "end .als"; break; } case REQ_rr: // groff(7) "Remove number Register" { qCDebug(KIO_MAN_LOG) << "start .rr"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty origin string to remove/rename: "; break; } c = skip_till_newline(c); QMap ::iterator it = s_numberDefinitionMap.find(name); if (it == s_numberDefinitionMap.end()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: trying to remove inexistant number register: "; } else { s_numberDefinitionMap.remove(name); } qCDebug(KIO_MAN_LOG) << "end .rr"; break; } case REQ_rnn: // groff(7) "ReName Number register" { qCDebug(KIO_MAN_LOG) << "start .rnn"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty origin to remove/rename number register"; break; } while (*c && isspace(*c) && *c != '\n') ++c; const QByteArray name2(scan_identifier(c)); if (name2.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty destination to rename number register"; break; } c = skip_till_newline(c); QMap::iterator it = s_numberDefinitionMap.find(name); if (it == s_numberDefinitionMap.end()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find number register to rename" << BYTEARRAY(name); } else { NumberDefinition def = (*it); s_numberDefinitionMap.remove(name); // ### QT4: removeAll s_numberDefinitionMap.insert(name2, def); } qCDebug(KIO_MAN_LOG) << "end .rnn"; break; } case REQ_aln: // groff(7) "ALias Number Register" { /* * Note an alias is supposed to be something like a hard link * However to make it simplier, we only copy the string. */ // Be careful: unlike .rnn, the destination is first, origin is second qCDebug(KIO_MAN_LOG) << "start .aln"; c += j; const QByteArray name(scan_identifier(c)); if (name.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty destination number register to alias"; break; } while (*c && isspace(*c) && *c != '\n') ++c; const QByteArray name2(scan_identifier(c)); if (name2.isEmpty()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: empty origin number register to alias"; break; } qCDebug(KIO_MAN_LOG) << "Alias " << BYTEARRAY(name2) << " to " << BYTEARRAY(name); c = skip_till_newline(c); if (name == name2) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: same origin and destination number register to alias: " << BYTEARRAY(name); break; } // Second parameter is origin (unlike in .rnn) QMap::iterator it = s_numberDefinitionMap.find(name2); if (it == s_numberDefinitionMap.end()) { qCDebug(KIO_MAN_LOG) << "EXCEPTION: cannot find string to make alias: " << BYTEARRAY(name2); } else { NumberDefinition def = (*it); s_numberDefinitionMap.insert(name, def); } qCDebug(KIO_MAN_LOG) << "end .aln"; break; } case REQ_shift: // groff(7) "SHIFT parameter" { c += j; h = c; while (*h && *h != '\n' && isdigit(*h)) ++h; const char tempchar = *h; *h = 0; const QByteArray number(c); *h = tempchar; c = skip_till_newline(h); unsigned int result = 1; // Numbers of shifts to do if (!number.isEmpty()) { bool ok = false; result = number.toUInt(&ok); if (!ok || result < 1) result = 1; } for (unsigned int num = 0; num < result; ++num) { if (!s_argumentList.isEmpty()) s_argumentList.pop_front(); } break; } case REQ_while: // groff(7) "WHILE loop" { request_while(c, j, mandoc_command); break; } case REQ_break: // groff(7) Break out of a while loop. { c += j; break_the_while_loop = true; break; } case REQ_do: // groff(7) "DO command" { // ### HACK: we just replace do by a \n and a . *c = '\n'; c++; *c = '.'; // The . will be treated as next character break; } case REQ_nop: // groff(7) nop { c += j; break; } default: { if (mandoc_command && ((isupper(*c) && islower(*(c + 1))) || (islower(*c) && isupper(*(c + 1))))) { /* Let through any mdoc(7) commands that haven't * been delt with. * I don't want to miss anything out of the text. */ char buf[4] = { c[0], c[1], ' ', 0 }; out_html(buf); /* Print the command (it might just be text). */ c = c + j; trans_char(c, '"', '\a'); if (*c == '\n') c++; out_html(set_font("R")); - c = scan_troff(c, 1, NULL); + c = scan_troff(c, 1, nullptr); out_html(NEWLINE); if (fillout) curpos++; else curpos = 0; } else c = skip_till_newline(c); break; } } } } if (fillout) { out_html(NEWLINE); curpos++; } return c; } //--------------------------------------------------------------------- static int contained_tab = 0; static bool mandoc_line = false; // Signals whether to look for embedded mandoc commands. static char *scan_troff(char *c, bool san, char **result) { /* san : stop at newline */ char *h; char intbuff[NULL_TERMINATED(MED_STR_MAX)]; int ibp = 0; #define FLUSHIBP if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; } char *exbuffer; int exbuffpos, exbuffmax, exnewline_for_fun; bool exscaninbuff; int usenbsp = 0; exbuffer = buffer; exbuffpos = buffpos; exbuffmax = buffmax; exnewline_for_fun = newline_for_fun; exscaninbuff = scaninbuff; newline_for_fun = 0; if (result) { if (*result) { buffer = *result; buffpos = qstrlen(buffer); buffmax = buffpos; } else { buffer = new char[LARGE_STR_MAX + 1]; buffpos = 0; buffmax = LARGE_STR_MAX; } scaninbuff = true; } h = c; // ### FIXME below are too many tests that may go before the position of c /* start scanning */ while (h && *h && (!san || newline_for_fun || (*h != '\n')) && !break_the_while_loop) { if (*h == escapesym) { h++; FLUSHIBP; // ###HACK: I think after escape expansion, the line should be reparsed // (this seems to be what troff does), but it would double-escape // HTML chars, e.g. the first escape produces ""); curpos = 0; still_dd = false; } switch (*h) { case '&': intbuff[ibp++] = '&'; intbuff[ibp++] = 'a'; intbuff[ibp++] = 'm'; intbuff[ibp++] = 'p'; intbuff[ibp++] = ';'; curpos++; break; case '<': intbuff[ibp++] = '&'; intbuff[ibp++] = 'l'; intbuff[ibp++] = 't'; intbuff[ibp++] = ';'; curpos++; break; case '>': intbuff[ibp++] = '&'; intbuff[ibp++] = 'g'; intbuff[ibp++] = 't'; intbuff[ibp++] = ';'; curpos++; break; case '"': intbuff[ibp++] = '&'; intbuff[ibp++] = 'q'; intbuff[ibp++] = 'u'; intbuff[ibp++] = 'o'; intbuff[ibp++] = 't'; intbuff[ibp++] = ';'; curpos++; break; case '\n': if (h != c && h[-1] == '\n' && fillout) { intbuff[ibp++] = '<'; intbuff[ibp++] = 'P'; intbuff[ibp++] = '>'; } if (contained_tab && fillout) { intbuff[ibp++] = '<'; intbuff[ibp++] = 'B'; intbuff[ibp++] = 'R'; intbuff[ibp++] = '>'; } contained_tab = 0; curpos = 0; usenbsp = 0; intbuff[ibp++] = '\n'; FLUSHIBP; break; case '\t': { int curtab = 0; contained_tab = 1; FLUSHIBP; /* like a typewriter, not like TeX */ tabstops[19] = curpos + 1; while (curtab < maxtstop && tabstops[curtab] <= curpos) curtab++; if (curtab < maxtstop) { if (!fillout) { while (curpos < tabstops[curtab]) { intbuff[ibp++] = ' '; if (ibp > 480) { FLUSHIBP; } curpos++; } } else { out_html(""); while (curpos < tabstops[curtab]) { out_html(" "); curpos++; } out_html(""); } } } break; default: if (*h == ' ' && (h[-1] == '\n' || usenbsp)) { FLUSHIBP; if (!usenbsp && fillout) { out_html("
    "); curpos = 0; } usenbsp = fillout; if (usenbsp) out_html(" "); else intbuff[ibp++] = ' '; } else if (*h > 31 && *h < 127) intbuff[ibp++] = *h; else if (((unsigned char)(*h)) > 127) { intbuff[ibp++] = *h; } curpos++; break; } if (ibp > (MED_STR_MAX - 20)) FLUSHIBP; h++; } } FLUSHIBP; if (buffer) buffer[buffpos] = '\0'; if (san && h && *h) h++; newline_for_fun = exnewline_for_fun; if (result) { *result = buffer; buffer = exbuffer; buffpos = exbuffpos; buffmax = exbuffmax; scaninbuff = exscaninbuff; } return h; } //--------------------------------------------------------------------- static char *scan_troff_mandoc(char *c, bool san, char **result) { char *ret; char *end = c; bool oldval = mandoc_line; mandoc_line = true; while (*end && *end != '\n') { end++; } if (end > c + 2 && ispunct(*(end - 1)) && isspace(*(end - 2)) && *(end - 2) != '\n') { /* Don't format lonely punctuation E.g. in "xyz ," format * the xyz and then append the comma removing the space. */ *(end - 2) = '\n'; ret = scan_troff(c, san, result); *end = 0; out_html(end - 1); // output the punct char *end = '\n'; ret = end; } else { ret = scan_troff(c, san, result); } mandoc_line = oldval; return ret; } //--------------------------------------------------------------------- // Entry point void scan_man_page(const char *man_page) { if (!man_page) return; qCDebug(KIO_MAN_LOG) << "Start scanning man page"; // ### Do more init // Unlike man2html, we actually call this several times, hence the need to // properly cleanup all those static vars s_ifelseval.clear(); s_characterDefinitionMap.clear(); InitCharacterDefinitions(); s_stringDefinitionMap.clear(); InitStringDefinitions(); s_numberDefinitionMap.clear(); InitNumberDefinitions(); s_argumentList.clear(); listItemStack.clear(); section = 0; s_dollarZero = ""; // No macro called yet! mandoc_name = ""; output_possible = false; int strLength = qstrlen(man_page); char *buf = new char[strLength + 2]; qstrcpy(buf + 1, man_page); buf[0] = '\n'; qCDebug(KIO_MAN_LOG) << "Parse man page"; - scan_troff(buf + 1, 0, NULL); + scan_troff(buf + 1, 0, nullptr); qCDebug(KIO_MAN_LOG) << "Man page parsed!"; while (itemdepth || dl_set[itemdepth]) { checkListStack(); out_html("\n"); if (dl_set[itemdepth]) dl_set[itemdepth] = 0; else if (itemdepth > 0) itemdepth--; } out_html(set_font("R")); out_html(change_to_size(0)); if (!fillout) { fillout = 1; out_html(""); } out_html(NEWLINE); if (section) { output_real("
  • \n"); section = 0; } if (output_possible) { // The output is buggy wrt to how divs are handled. Fixing it would // require closing divs before other block-level elements are output, // and I do not feel like going to find them all. output_real("
    \n"); output_real("
    \n"); #ifdef SIMPLE_MAN2HTML output_real("Generated by kio_man"); #else output_real("Generated by kio_man version "); output_real(QString(KDE_VERSION_STRING).toHtmlEscaped().toLocal8Bit()); #endif output_real("
    \n\n"); output_real("\n\n"); } delete [] buf; // Release memory s_characterDefinitionMap.clear(); s_stringDefinitionMap.clear(); s_numberDefinitionMap.clear(); s_argumentList.clear(); // reinit static variables for reuse delete [] buffer; - buffer = 0; + buffer = nullptr; escapesym = '\\'; nobreaksym = '\''; controlsym = '.'; fieldsym = 0; padsym = 0; buffpos = 0; buffmax = 0; scaninbuff = false; itemdepth = 0; for (int i = 0; i < 20; i++) dl_set[i] = 0; still_dd = false; for (int i = 0; i < 12; i++) tabstops[i] = (i + 1) * 8; maxtstop = 12; curpos = 0; mandoc_name_count = 0; } //--------------------------------------------------------------------- #ifdef SIMPLE_MAN2HTML void output_real(const char *insert) { std::cout << insert; } char *read_man_page(const char *filename) { - char *man_buf = NULL; + char *man_buf = nullptr; - FILE *man_stream = NULL; + FILE *man_stream = nullptr; struct stat stbuf; size_t buf_size; if (stat(filename, &stbuf) == -1) { std::cerr << "read_man_page: can not find " << filename << std::endl; - return NULL; + return nullptr; } if (!S_ISREG(stbuf.st_mode)) { std::cerr << "read_man_page: no file " << filename << std::endl; - return NULL; + return nullptr; } buf_size = stbuf.st_size; man_buf = new char[buf_size + 5]; man_stream = fopen(filename, "r"); if (man_stream) { man_buf[0] = '\n'; if (fread(man_buf + 1, 1, buf_size, man_stream) == buf_size) { man_buf[buf_size] = '\n'; man_buf[buf_size + 1] = man_buf[buf_size + 2] = '\0'; } else { delete [] man_buf; - man_buf = NULL; + man_buf = nullptr; } fclose(man_stream); } return man_buf; } #ifndef KIO_MAN_TEST int main(int argc, char **argv) { if (argc < 2) { std::cerr << "call: " << argv[0] << " \n"; return 1; } if (chdir(argv[1])) { char *buf = read_man_page(argv[1]); if (buf) { scan_man_page(buf); delete [] buf; } } else { DIR *dir = opendir("."); struct dirent *ent; - while ((ent = readdir(dir)) != NULL) + while ((ent = readdir(dir)) != nullptr) { std::cerr << "converting " << ent->d_name << std::endl; char *buf = read_man_page(ent->d_name); if (buf) { scan_man_page(buf); delete [] buf; } } closedir(dir); } return 0; } #endif #endif // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/mtp/devicecache.cpp b/mtp/devicecache.cpp index 48be7267..ce845bb7 100644 --- a/mtp/devicecache.cpp +++ b/mtp/devicecache.cpp @@ -1,230 +1,230 @@ /* Cache for recently used devices. Copyright (C) 2012 Philipp Schmidt 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, see . */ #include "devicecache.h" #include "kio_mtp_helpers.h" // #include // #include #include #include #include /** * Creates a Cached Device that has a predefined lifetime (default: 10000 msec)s * The lifetime is reset every time the device is accessed. After it expires it * will be released. * * @param device The LIBMTP_mtpdevice_t pointer to cache * @param udi The UDI of the new device to cache */ CachedDevice::CachedDevice(LIBMTP_mtpdevice_t *device, LIBMTP_raw_device_t *rawdevice, const QString udi, qint32 timeout) { this->timeout = timeout; this->mtpdevice = device; this->rawdevice = *rawdevice; this->udi = udi; char *deviceName = LIBMTP_Get_Friendlyname(device); char *deviceModel = LIBMTP_Get_Modelname(device); // prefer friendly devicename over model if (!deviceName) { name = QString::fromUtf8(deviceModel); } else { name = QString::fromUtf8(deviceName); } qCDebug(LOG_KIO_MTP) << "Created device " << name << " with udi=" << udi << " and timeout " << timeout; } CachedDevice::~CachedDevice() { LIBMTP_Release_Device(mtpdevice); } LIBMTP_mtpdevice_t *CachedDevice::getDevice() { LIBMTP_mtpdevice_t *device = mtpdevice; if (!device->storage) { qCDebug(LOG_KIO_MTP) << "reopen mtpdevice if we have no storage found"; LIBMTP_Release_Device(mtpdevice); mtpdevice = LIBMTP_Open_Raw_Device_Uncached(&rawdevice); } return mtpdevice; } const QString CachedDevice::getName() { return name; } const QString CachedDevice::getUdi() { return udi; } DeviceCache::DeviceCache(qint32 timeout, QObject *parent) : QEventLoop(parent) { this->timeout = timeout; notifier = Solid::DeviceNotifier::instance(); connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); foreach(Solid::Device solidDevice, Solid::Device::listFromType(Solid::DeviceInterface::PortableMediaPlayer, QString())) { checkDevice(solidDevice); } } DeviceCache::~DeviceCache() { processEvents(); // Release devices foreach(QString udi, udiCache.keys()) { deviceRemoved(udi); } } void DeviceCache::checkDevice(Solid::Device solidDevice) { if (!udiCache.contains(solidDevice.udi())) { qCDebug(LOG_KIO_MTP) << "new device, getting raw devices"; Solid::GenericInterface *iface = solidDevice.as(); if (!iface) { qCDebug( LOG_KIO_MTP ) << "Solid device " << solidDevice.udi() << " has NOT a Solid::GenericInterface"; return; } const QMap &properties = iface->allProperties(); const uint32_t solidBusNum = properties.value ( QLatin1String ( "BUSNUM" ) ).toUInt(); const uint32_t solidDevNum = properties.value ( QLatin1String ( "DEVNUM" ) ).toUInt(); - LIBMTP_raw_device_t *rawdevices = 0; + LIBMTP_raw_device_t *rawdevices = nullptr; int numrawdevices; LIBMTP_error_number_t err; err = LIBMTP_Detect_Raw_Devices(&rawdevices, &numrawdevices); switch (err) { case LIBMTP_ERROR_CONNECTING: qCWarning(LOG_KIO_MTP) << "There has been an error connecting to the devices"; break; case LIBMTP_ERROR_MEMORY_ALLOCATION: qCWarning(LOG_KIO_MTP) << "Encountered a Memory Allocation Error"; break; case LIBMTP_ERROR_NONE: { qCDebug(LOG_KIO_MTP) << "No Error, continuing"; for (int i = 0; i < numrawdevices; i++) { LIBMTP_raw_device_t *rawDevice = &rawdevices[i]; uint32_t rawBusNum = rawDevice->bus_location; uint32_t rawDevNum = rawDevice->devnum; if (rawBusNum == solidBusNum && rawDevNum == solidDevNum) { qCDebug(LOG_KIO_MTP) << "Found device matching the Solid description"; LIBMTP_mtpdevice_t *mtpDevice = LIBMTP_Open_Raw_Device_Uncached(rawDevice); if (udiCache.find(solidDevice.udi()) == udiCache.end()) { CachedDevice *cDev = new CachedDevice(mtpDevice, rawDevice, solidDevice.udi(), timeout); udiCache.insert(solidDevice.udi(), cDev); nameCache.insert(cDev->getName(), cDev); } } } } break; case LIBMTP_ERROR_GENERAL: default: qCWarning(LOG_KIO_MTP) << "Unknown connection error"; break; } free(rawdevices); } } void DeviceCache::deviceAdded(const QString &udi) { qCDebug(LOG_KIO_MTP) << "New device attached with udi=" << udi << ". Checking if PortableMediaPlayer..."; Solid::Device device(udi); if (device.isDeviceInterface(Solid::DeviceInterface::PortableMediaPlayer)) { qCDebug(LOG_KIO_MTP) << "SOLID: New Device with udi=" << udi << "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; checkDevice(device); } } void DeviceCache::deviceRemoved(const QString &udi) { if (udiCache.contains(udi)) { qCDebug(LOG_KIO_MTP) << "SOLID: Device with udi=" << udi << " removed. ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; CachedDevice *cDev = udiCache.value(udi); udiCache.remove(cDev->getUdi()); nameCache.remove(cDev->getName()); delete cDev; } } QHash DeviceCache::getAll() { qCDebug(LOG_KIO_MTP) << "getAll()"; processEvents(); return nameCache; } bool DeviceCache::contains(QString string, bool isUdi) { processEvents(); if (isUdi) { return udiCache.find(string) != udiCache.end(); } else { return nameCache.find(string) != nameCache.end(); } } CachedDevice *DeviceCache::get(const QString &string, bool isUdi) { processEvents(); if (isUdi) { return udiCache.value(string); } else { return nameCache.value(string); } } int DeviceCache::size() { processEvents(); return nameCache.size(); } #include "devicecache.moc" diff --git a/mtp/devicecache.h b/mtp/devicecache.h index dc1038fb..cb1343c4 100644 --- a/mtp/devicecache.h +++ b/mtp/devicecache.h @@ -1,86 +1,86 @@ /* Cache for recently used devices. Copyright (C) 2012 Philipp Schmidt 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, see . */ #ifndef KIO_MTP_DEVICE_CACHE_H #define KIO_MTP_DEVICE_CACHE_H #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) class CachedDevice : public QObject { Q_OBJECT public: explicit CachedDevice(LIBMTP_mtpdevice_t *device, LIBMTP_raw_device_t *rawdevice, const QString udi, qint32 timeout); virtual ~CachedDevice(); LIBMTP_mtpdevice_t *getDevice(); const QString getName(); const QString getUdi(); private: qint32 timeout; QTimer *timer; LIBMTP_mtpdevice_t *mtpdevice; LIBMTP_raw_device_t rawdevice; QString name; QString udi; }; class DeviceCache : public QEventLoop { Q_OBJECT public: - DeviceCache(qint32 timeout, QObject *parent = 0); + DeviceCache(qint32 timeout, QObject *parent = nullptr); virtual ~DeviceCache(); QHash< QString, CachedDevice * > getAll(); CachedDevice *get(const QString &string, bool isUdi = false); bool contains(QString string, bool isUdi = false); int size(); private slots: void deviceAdded(const QString &udi); void deviceRemoved(const QString &udi); private: void checkDevice(Solid::Device solidDevice); /** * Fields in order: Devicename (QString), expiration Timer, pointer to device */ QHash< QString, CachedDevice * > nameCache, udiCache; Solid::DeviceNotifier *notifier; qint32 timeout; }; #endif // KIO_MTP_DEVICE_CACHE_H diff --git a/mtp/filecache.h b/mtp/filecache.h index a611379c..4e02ca13 100644 --- a/mtp/filecache.h +++ b/mtp/filecache.h @@ -1,81 +1,81 @@ /* Cache for recent files accessed. Copyright (C) 2012 Philipp Schmidt 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. */ #ifndef KIO_MTP_FILE_CACHE_H #define KIO_MTP_FILE_CACHE_H #define KIO_MTP 7000 #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(LOG_KIO_MTP) /** * @class FileCache Implements a time based cache for file ids, mapping their path to their ID. Does _not_ store the device they are on. */ class FileCache : public QObject { Q_OBJECT public: - explicit FileCache(QObject *parent = 0); + explicit FileCache(QObject *parent = nullptr); /** * Returns the ID of the item at the given path, else 0. * Automatically discards old items. * * @param path The Path to query the cache for * @return The ID of the Item if it exists, else 0 */ uint32_t queryPath(const QString &path, int timeToLive = 60); /** * Adds a Path to the Cache with the given id and ttl. * * @param path The path of the file/folder * @param id The file ID on the storage * @param timeToLive The time in seconds the entry should be valid */ void addPath(const QString &path, uint32_t id, int timeToLive = 60); /** * Remove the given path from the cache, i.e. if it got deleted * * @param path The path that should be removed */ void removePath(const QString &path); // private slots: // void insertItem( const QString& path, QPair item ); // void removeItem( const QString& path ); // // signals: // void s_insertItem( const QString& path, QPair item ); // void s_removeItem( const QString& path ); private: QHash > cache; }; #endif // KIO_MTP_FILE_CACHE_H diff --git a/mtp/kio_mtp.cpp b/mtp/kio_mtp.cpp index 541268a1..309266df 100644 --- a/mtp/kio_mtp.cpp +++ b/mtp/kio_mtp.cpp @@ -1,960 +1,960 @@ /* * Main implementation for KIO-MTP * Copyright (C) 2012 Philipp Schmidt * * 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_mtp.h" #include "kio_mtp_helpers.h" // #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// ///////////////////////////// Slave Implementation /////////////////////////// ////////////////////////////////////////////////////////////////////////////// Q_LOGGING_CATEGORY(LOG_KIO_MTP, "kde.kio-mtp") extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); app.setApplicationName(QLatin1String("kio_mtp")); if (argc != 4) { fprintf(stderr, "Usage: kio_mtp protocol domain-socket1 domain-socket2\n"); exit(-1); } MTPSlave slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(LOG_KIO_MTP) << "Slave EventLoop ended"; return 0; } MTPSlave::MTPSlave(const QByteArray &pool, const QByteArray &app) : SlaveBase("mtp", pool, app) { LIBMTP_Init(); qCDebug(LOG_KIO_MTP) << "Slave started"; deviceCache = new DeviceCache(60000); fileCache = new FileCache(this); qCDebug(LOG_KIO_MTP) << "Caches created"; } MTPSlave::~MTPSlave() { qCDebug(LOG_KIO_MTP) << "Slave destroyed"; delete fileCache; delete deviceCache; } /** * @brief Get's the correct object from the device. * @param pathItems A QStringList containing the items of the filepath * @return QPair with the object and its device. pair.first is a nullpointer if the object doesn't exist or for root or, depending on the pathItems size device (1), storage (2) or file (>=3) */ QPair MTPSlave::getPath(const QString &path) { QStringList pathItems = path.split(QLatin1Char('/'), QString::SkipEmptyParts); qCDebug(LOG_KIO_MTP) << path << pathItems.size(); QPair ret; // Don' handle the root directory if (pathItems.size() <= 0) { return ret; } if (deviceCache->contains(pathItems.at(0))) { LIBMTP_mtpdevice_t *device = deviceCache->get(pathItems.at(0))->getDevice(); // return specific device if (pathItems.size() == 1) { ret.first = device; ret.second = device; qCDebug(LOG_KIO_MTP) << "returning LIBMTP_mtpdevice_t"; } if (pathItems.size() > 2) { // Query Cache after we have the device uint32_t c_fileID = fileCache->queryPath(path); if (c_fileID != 0) { qCDebug(LOG_KIO_MTP) << "Match found in cache, checking device"; LIBMTP_file_t *file = LIBMTP_Get_Filemetadata(device, c_fileID); if (file) { qCDebug(LOG_KIO_MTP) << "Found file in cache"; ret.first = file; ret.second = device; qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t from cache"; return ret; } } // Query cache for parent else if (pathItems.size() > 3) { QString parentPath = convertToPath(pathItems, pathItems.size() - 1); uint32_t c_parentID = fileCache->queryPath(parentPath); qCDebug(LOG_KIO_MTP) << "Match for parent found in cache, checking device. Parent id = " << c_parentID; LIBMTP_file_t *parent = LIBMTP_Get_Filemetadata(device, c_parentID); if (parent) { qCDebug(LOG_KIO_MTP) << "Found parent in cache"; // fileCache->addPath( parentPath, c_parentID ); QMap files = getFiles(device, parent->storage_id, c_parentID); for (QMap::iterator it = files.begin(); it != files.end(); ++it) { QString filePath = parentPath; filePath.append(QString::fromUtf8("/")).append(it.key()); fileCache->addPath(filePath, it.value()->item_id); } if (files.contains(pathItems.last())) { LIBMTP_file_t *file = files.value(pathItems.last()); ret.first = file; ret.second = device; qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t from cached parent" ; fileCache->addPath(path, file->item_id); } return ret; } } } QMap storages = getDevicestorages(device); if (pathItems.size() > 1 && storages.contains(pathItems.at(1))) { LIBMTP_devicestorage_t *storage = storages.value(pathItems.at(1)); if (pathItems.size() == 2) { ret.first = storage; ret.second = device; qCDebug(LOG_KIO_MTP) << "returning LIBMTP_devicestorage_t"; return ret; } int currentLevel = 2, currentParent = 0xFFFFFFFF; QMap files; // traverse further while depth not reached while (currentLevel < pathItems.size()) { files = getFiles(device, storage->id, currentParent); if (files.contains(pathItems.at(currentLevel))) { currentParent = files.value(pathItems.at(currentLevel))->item_id; } else { qCDebug(LOG_KIO_MTP) << "returning LIBMTP_file_t using tree walk"; return ret; } currentLevel++; } ret.first = LIBMTP_Get_Filemetadata(device, currentParent); ret.second = device; fileCache->addPath(path, currentParent); } } return ret; } int MTPSlave::checkUrl(const QUrl &url, bool redirect) { qCDebug(LOG_KIO_MTP) << url; if (url.path().startsWith(QLatin1String("udi=")) && redirect) { QString udi = url.adjusted(QUrl::StripTrailingSlash).path().remove(0, 4); qCDebug(LOG_KIO_MTP) << "udi = " << udi; if (deviceCache->contains(udi, true)) { QUrl newUrl; newUrl.setScheme(QLatin1String("mtp")); newUrl.setPath(QLatin1Char('/') + deviceCache->get(udi, true)->getName()); redirection(newUrl); return 1; } else { return 2; } } else if (url.path().startsWith(QLatin1Char('/'))) { return 0; } return -1; } QString MTPSlave::urlDirectory(const QUrl &url, bool appendTrailingSlash) { if (!appendTrailingSlash) { return url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).path(); } return url.adjusted(QUrl::RemoveFilename).path(); } QString MTPSlave::urlFileName(const QUrl &url) { return url.fileName(); } void MTPSlave::listDir(const QUrl &url) { qCDebug(LOG_KIO_MTP) << url.path(); int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); UDSEntry entry; // list devices if (pathItems.size() == 0) { qCDebug(LOG_KIO_MTP) << "Root directory, listing devices"; totalSize(deviceCache->size()); foreach(CachedDevice * cachedDevice, deviceCache->getAll().values()) { LIBMTP_mtpdevice_t *device = cachedDevice->getDevice(); getEntry(entry, device); listEntry(entry); entry.clear(); } qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Devices"; finished(); } // traverse into device else if (deviceCache->contains(pathItems.at(0))) { QPair pair = getPath(url.path()); UDSEntry entry; if (pair.first) { LIBMTP_mtpdevice_t *device = pair.second; // Device, list storages if (pathItems.size() == 1) { QMap storages = getDevicestorages(device); qCDebug(LOG_KIO_MTP) << "Listing storages for device " << pathItems.at(0); totalSize(storages.size()); if (storages.size() > 0) { foreach(const QString & storageName, storages.keys()) { getEntry(entry, storages.value(storageName)); listEntry(entry); entry.clear(); } finished(); qCDebug(LOG_KIO_MTP) << "[SUCCESS] :: Storages"; } else { warning(i18n("No Storages found. Make sure your device is unlocked and has MTP enabled in its USB connection settings.")); } } // Storage, list files and folders of storage root else { QMap files; if (pathItems.size() == 2) { qCDebug(LOG_KIO_MTP) << "Getting storage root listing"; LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t *)pair.first; - qCDebug(LOG_KIO_MTP) << "We have a storage:" << (storage == NULL); + qCDebug(LOG_KIO_MTP) << "We have a storage:" << (storage == nullptr); files = getFiles(device, storage->id); } else { LIBMTP_file_t *parent = (LIBMTP_file_t *)pair.first; files = getFiles(device, parent->storage_id, parent->item_id); } for (QMap::iterator it = files.begin(); it != files.end(); ++it) { LIBMTP_file_t *file = it.value(); QString filePath; if (url.path().endsWith(QLatin1Char('/'))) { filePath = url.path().append(it.key()); } else { filePath = url.path().append(QLatin1Char('/')).append(it.key()); } fileCache->addPath(filePath, file->item_id); getEntry(entry, file); listEntry(entry); entry.clear(); } finished(); qCDebug(LOG_KIO_MTP) << "[SUCCESS] Files"; } } else { error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); qCDebug(LOG_KIO_MTP) << "[ERROR]"; } } else { error(ERR_CANNOT_ENTER_DIRECTORY, url.path()); qCDebug(LOG_KIO_MTP) << "[ERROR]"; } } void MTPSlave::stat(const QUrl &url) { qCDebug(LOG_KIO_MTP) << url.path(); int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); QPair pair = getPath(url.path()); UDSEntry entry; if (pair.first) { // Root if (pathItems.size() < 1) { entry.insert(UDSEntry::UDS_NAME, QLatin1String("mtp:///")); entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); } // Device else if (pathItems.size() < 2) { getEntry(entry, pair.second); } // Storage else if (pathItems.size() < 3) { getEntry(entry, (LIBMTP_devicestorage_t *) pair.first); } // Folder/File else { getEntry(entry, (LIBMTP_file_t *) pair.first); } } statEntry(entry); finished(); } void MTPSlave::mimetype(const QUrl &url) { int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.path()); return; default: error(ERR_MALFORMED_URL, url.path()); return; } qCDebug(LOG_KIO_MTP) << url.path(); QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); QPair pair = getPath(url.path()); if (pair.first) { // NOTE the difference between calling mimetype and mimeType if (pathItems.size() > 2) { mimeType(getMimetype(((LIBMTP_file_t *) pair.first)->filetype)); } else { mimeType(QString::fromLatin1("inode/directory")); } } else { error(ERR_DOES_NOT_EXIST, url.path()); return; } } void MTPSlave::put(const QUrl &url, int, JobFlags flags) { int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } qCDebug(LOG_KIO_MTP) << url.path(); QStringList destItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // Can't copy to root or device, needs storage if (destItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, url.path()); return; } if (!(flags & KIO::Overwrite) && getPath(url.path()).first) { error(ERR_FILE_ALREADY_EXIST, url.path()); return; } destItems.takeLast(); QPair pair = getPath(urlDirectory(url)); if (!pair.first) { error(ERR_DOES_NOT_EXIST, url.path()); return; } LIBMTP_mtpdevice_t *device = pair.second; LIBMTP_file_t *parent = (LIBMTP_file_t *) pair.first; if (parent->filetype != LIBMTP_FILETYPE_FOLDER) { error(ERR_IS_FILE, urlDirectory(url)); return; } // We did get a total size from the application if (hasMetaData(QLatin1String("sourceSize"))) { qCDebug(LOG_KIO_MTP) << "direct put"; LIBMTP_file_t *file = LIBMTP_new_file_t(); file->parent_id = parent->item_id; file->filename = strdup(urlFileName(url).toUtf8().data()); file->filetype = getFiletype(urlFileName(url)); file->filesize = metaData(QLatin1String("sourceSize")).toULongLong(); file->modificationdate = QDateTime::currentDateTime().toTime_t(); file->storage_id = parent->storage_id; qCDebug(LOG_KIO_MTP) << "Sending file" << file->filename; int ret = LIBMTP_Send_File_From_Handler(device, &dataGet, this, file, &dataProgress, this); LIBMTP_destroy_file_t(file); if (ret != 0) { error(KIO::ERR_COULD_NOT_WRITE, urlFileName(url)); LIBMTP_Dump_Errorstack(device); LIBMTP_Clear_Errorstack(device); return; } } // We need to get the entire file first, then we can upload else { qCDebug(LOG_KIO_MTP) << "use temp file"; QTemporaryFile temp; QByteArray buffer; int len = 0; do { dataReq(); len = readData(buffer); temp.write(buffer); } while (len > 0); QFileInfo info(temp); LIBMTP_file_t *file = LIBMTP_new_file_t(); file->parent_id = parent->item_id; file->filename = strdup(urlFileName(url).toUtf8().data()); file->filetype = getFiletype(urlFileName(url)); file->filesize = info.size(); file->modificationdate = QDateTime::currentDateTime().toTime_t(); file->storage_id = parent->storage_id; - int ret = LIBMTP_Send_File_From_File_Descriptor(device, temp.handle(), file, NULL, NULL); + int ret = LIBMTP_Send_File_From_File_Descriptor(device, temp.handle(), file, nullptr, nullptr); LIBMTP_destroy_file_t(file); if (ret != 0) { error(KIO::ERR_COULD_NOT_WRITE, urlFileName(url)); return; } finished(); } } void MTPSlave::get(const QUrl &url) { int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } qCDebug(LOG_KIO_MTP) << url.path(); QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // File if (pathItems.size() > 2) { QPair pair = getPath(url.path()); if (pair.first) { LIBMTP_file_t *file = (LIBMTP_file_t *) pair.first; mimeType(getMimetype(file->filetype)); totalSize(file->filesize); LIBMTP_mtpdevice_t *device = pair.second; int ret = LIBMTP_Get_File_To_Handler(device, file->item_id, &dataPut, this, &dataProgress, this); if (ret != 0) { error(ERR_COULD_NOT_READ, url.path()); return; } data(QByteArray()); finished(); } else { error(ERR_DOES_NOT_EXIST, url.path()); } } else { error(ERR_UNSUPPORTED_ACTION, url.path()); } } void MTPSlave::copy(const QUrl &src, const QUrl &dest, int, JobFlags flags) { qCDebug(LOG_KIO_MTP) << src.path() << dest.path(); if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("mtp")) { qCDebug(LOG_KIO_MTP) << "Copy on device: Not supported"; // MTP doesn't support moving files directly on the device, so we have to download and then upload... error(ERR_UNSUPPORTED_ACTION, i18n("Cannot copy/move files on the device itself")); } else if (src.scheme() == QLatin1String("file") && dest.scheme() == QLatin1String("mtp")) { int check = checkUrl(dest); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, dest.path()); return; } QStringList destItems = dest.path().split(QLatin1Char('/') , QString::SkipEmptyParts); // Can't copy to root or device, needs storage if (destItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, dest.path()); return; } qCDebug(LOG_KIO_MTP) << "Copy file " << urlFileName(src) << "from filesystem to device" << urlDirectory(src, true) << urlDirectory(dest, true); if (!(flags & KIO::Overwrite) && getPath(dest.path()).first) { error(ERR_FILE_ALREADY_EXIST, dest.path()); return; } destItems.takeLast(); QPair pair = getPath(urlDirectory(dest)); if (!pair.first) { error(ERR_DOES_NOT_EXIST, urlDirectory(dest, true)); return; } LIBMTP_mtpdevice_t *device = pair.second; uint32_t parent_id = 0xFFFFFFFF, storage_id = 0; if (destItems.size() == 2) { LIBMTP_devicestorage_t *storage = (LIBMTP_devicestorage_t *) pair.first; storage_id = storage->id; } else { LIBMTP_file_t *parent = (LIBMTP_file_t *) pair.first; storage_id = parent->storage_id; parent_id = parent->item_id; if (parent->filetype != LIBMTP_FILETYPE_FOLDER) { error(ERR_IS_FILE, urlDirectory(dest)); return; } } QFileInfo source(src.path()); LIBMTP_file_t *file = LIBMTP_new_file_t(); file->parent_id = parent_id; file->filename = strdup(urlFileName(dest).toUtf8().data()); file->filetype = getFiletype(urlFileName(dest)); file->filesize = source.size(); file->modificationdate = source.lastModified().toTime_t(); file->storage_id = storage_id; qCDebug(LOG_KIO_MTP) << "Sending file" << file->filename << "with size" << file->filesize; totalSize(source.size()); int ret = LIBMTP_Send_File_From_File(device, src.path().toUtf8().data(), file, (LIBMTP_progressfunc_t) &dataProgress, this); LIBMTP_destroy_file_t(file); if (ret != 0) { error(KIO::ERR_COULD_NOT_WRITE, urlFileName(dest)); LIBMTP_Dump_Errorstack(device); LIBMTP_Clear_Errorstack(device); return; } qCDebug(LOG_KIO_MTP) << "Sent file"; } else if (src.scheme() == QLatin1String("mtp") && dest.scheme() == QLatin1String("file")) { int check = checkUrl(src); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, src.path()); return; } qCDebug(LOG_KIO_MTP) << "Copy file " << urlFileName(src) << "from device to filesystem" << urlDirectory(src, true) << urlDirectory(dest, true); QFileInfo destination(dest.path()); if (!(flags & KIO::Overwrite) && destination.exists()) { error(ERR_FILE_ALREADY_EXIST, dest.path()); return; } QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); // Can't copy to root or device, needs storage if (srcItems.size() < 2) { error(ERR_UNSUPPORTED_ACTION, src.path()); return; } QPair pair = getPath(src.path()); if (!pair.first) { error(ERR_COULD_NOT_READ, src.path()); return; } LIBMTP_mtpdevice_t *device = pair.second; LIBMTP_file_t *source = (LIBMTP_file_t *) pair.first; if (source->filetype == LIBMTP_FILETYPE_FOLDER) { error(ERR_IS_DIRECTORY, urlDirectory(src)); return; } qCDebug(LOG_KIO_MTP) << "Getting file" << source->filename << urlFileName(dest) << source->filesize; totalSize(source->filesize); int ret = LIBMTP_Get_File_To_File(device, source->item_id, dest.path().toUtf8().data(), (LIBMTP_progressfunc_t) &dataProgress, this); if (ret != 0) { error(KIO::ERR_COULD_NOT_WRITE, urlFileName(dest)); LIBMTP_Dump_Errorstack(device); LIBMTP_Clear_Errorstack(device); return; } struct utimbuf *times = (utimbuf *) malloc(sizeof(utimbuf)); times->actime = QDateTime::currentDateTime().toTime_t(); times->modtime = source->modificationdate; int result = utime(dest.path().toUtf8().data(), times); free(times); qCDebug(LOG_KIO_MTP) << "Sent file"; } finished(); } void MTPSlave::mkdir(const QUrl &url, int) { int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } qCDebug(LOG_KIO_MTP) << url.path(); QStringList pathItems = url.path().split(QLatin1Char('/') , QString::SkipEmptyParts); int pathDepth = pathItems.size(); if (pathItems.size() > 2 && !getPath(url.path()).first) { char *dirName = strdup(pathItems.takeLast().toUtf8().data()); LIBMTP_mtpdevice_t *device; LIBMTP_file_t *file; LIBMTP_devicestorage_t *storage; int ret = 0; QPair pair = getPath(urlDirectory(url)); if (pathDepth == 3) { //the folder need to be created straight to a storage device storage = (LIBMTP_devicestorage_t *) pair.first; device = pair.second; ret = LIBMTP_Create_Folder(device, dirName, 0xFFFFFFFF, storage->id); } else if (pair.first) { file = (LIBMTP_file_t *) pair.first; device = pair.second; if (file && file->filetype == LIBMTP_FILETYPE_FOLDER) { qCDebug(LOG_KIO_MTP) << "Found parent" << file->item_id << file->filename; qCDebug(LOG_KIO_MTP) << "Attempting to create folder" << dirName; ret = LIBMTP_Create_Folder(device, dirName, file->item_id, file->storage_id); } } if (ret != 0) { fileCache->addPath(url.path(), ret); finished(); return; } else { LIBMTP_Dump_Errorstack(device); LIBMTP_Clear_Errorstack(device); } } else { error(ERR_DIR_ALREADY_EXIST, url.path()); return; } error(ERR_COULD_NOT_MKDIR, url.path()); } void MTPSlave::del(const QUrl &url, bool) { int check = checkUrl(url); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, url.path()); return; } qCDebug(LOG_KIO_MTP) << url.path(); QStringList pathItems = url.path().split(QLatin1Char('/'), QString::SkipEmptyParts); if (pathItems.size() < 2) { error(ERR_CANNOT_DELETE, url.path()); return; } QPair pair = getPath(url.path()); LIBMTP_file_t *file = (LIBMTP_file_t *) pair.first; int ret = LIBMTP_Delete_Object(pair.second, file->item_id); LIBMTP_destroy_file_t (file); if (ret != 0) { error(ERR_CANNOT_DELETE, url.path()); return; } fileCache->removePath(url.path()); finished(); } void MTPSlave::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { int check = checkUrl(src); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, src.path()); return; } check = checkUrl(dest); switch (check) { case 0: break; default: error(ERR_MALFORMED_URL, dest.path()); return; } qCDebug(LOG_KIO_MTP) << src.path(); QStringList srcItems = src.path().split(QLatin1Char('/'), QString::SkipEmptyParts); QPair pair = getPath(src.path()); if (pair.first) { // Rename Device if (srcItems.size() == 1) { LIBMTP_Set_Friendlyname(pair.second, urlFileName(dest).toUtf8().data()); } // Rename Storage else if (srcItems.size() == 2) { error(ERR_CANNOT_RENAME, src.path()); return; } else { LIBMTP_file_t *destination = (LIBMTP_file_t *) getPath(dest.path()).first; LIBMTP_file_t *source = (LIBMTP_file_t *) pair.first; if (!(flags & KIO::Overwrite) && destination) { if (destination->filetype == LIBMTP_FILETYPE_FOLDER) { error(ERR_DIR_ALREADY_EXIST, dest.path()); } else { error(ERR_FILE_ALREADY_EXIST, dest.path()); } return; } int ret = LIBMTP_Set_File_Name(pair.second, source, urlFileName(dest).toUtf8().data()); if (ret != 0) { error(ERR_CANNOT_RENAME, src.path()); return; } else { fileCache->addPath(dest.path(), source->item_id); fileCache->removePath(src.path()); } LIBMTP_destroy_file_t (source); } finished(); } else { error(ERR_DOES_NOT_EXIST, src.path()); } } void MTPSlave::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); } } void MTPSlave::fileSystemFreeSpace(const QUrl &url) { qCDebug(LOG_KIO_MTP) << "fileSystemFreeSpace:" << url; const int check = checkUrl(url); switch (check) { case 0: break; case 1: finished(); return; case 2: error(ERR_DOES_NOT_EXIST, url.toDisplayString()); return; default: error(ERR_MALFORMED_URL, url.toDisplayString()); return; } const auto path = url.path(); const auto storagePath = path.section(QLatin1Char('/'), 0, 2, QString::SectionIncludeLeadingSep); if (storagePath.count(QLatin1Char('/')) != 2) { // /{device}/{storage} error(KIO::ERR_COULD_NOT_STAT, url.toDisplayString()); return; } const auto pair = getPath(storagePath); auto storage = (LIBMTP_devicestorage_t *)pair.first; if (!storage) { error(KIO::ERR_COULD_NOT_STAT, url.toDisplayString()); return; } setMetaData(QStringLiteral("total"), QString::number(storage->MaxCapacity)); setMetaData(QStringLiteral("available"), QString::number(storage->FreeSpaceInBytes)); finished(); } #include "kio_mtp.moc" diff --git a/mtp/kio_mtp_helpers.cpp b/mtp/kio_mtp_helpers.cpp index 2ff7918b..620d1b65 100644 --- a/mtp/kio_mtp_helpers.cpp +++ b/mtp/kio_mtp_helpers.cpp @@ -1,358 +1,358 @@ /* * Helper implementations for KIO-MTP * Copyright (C) 2013 Philipp Schmidt * * 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_mtp_helpers.h" int dataProgress(uint64_t const sent, uint64_t const, void const *const priv) { ((MTPSlave *) priv)->processedSize(sent); return 0; } /** * MTPDataPutFunc callback function, "puts" data from the device somewhere else */ uint16_t dataPut(void *, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen) { qCDebug(LOG_KIO_MTP) << "transferring" << sendlen << "bytes to data()"; ((MTPSlave *) priv)->data(QByteArray((char *) data, (int) sendlen)); *putlen = sendlen; return LIBMTP_HANDLER_RETURN_OK; } /** * MTPDataGetFunc callback function, "gets" data and puts it on the device */ uint16_t dataGet(void *, void *priv, uint32_t, unsigned char *data, uint32_t *gotlen) { ((MTPSlave *) priv)->dataReq(); QByteArray buffer; *gotlen = ((MTPSlave *) priv)->readData(buffer); qCDebug(LOG_KIO_MTP) << "transferring" << *gotlen << "bytes to data()"; data = (unsigned char *) buffer.data(); return LIBMTP_HANDLER_RETURN_OK; } QString convertToPath(const QStringList &pathItems, const int elements) { QString path; for (int i = 0; i < elements && elements <= pathItems.size(); i++) { path.append(QLatin1Char('/')); path.append(pathItems.at(i)); } return path; } QString getMimetype(LIBMTP_filetype_t filetype) { switch (filetype) { case LIBMTP_FILETYPE_FOLDER: return QLatin1String("inode/directory"); case LIBMTP_FILETYPE_WAV: return QLatin1String("audio/wav"); case LIBMTP_FILETYPE_MP3: return QLatin1String("audio/x-mp3"); case LIBMTP_FILETYPE_WMA: return QLatin1String("audio/x-ms-wma"); case LIBMTP_FILETYPE_OGG: return QLatin1String("audio/x-vorbis+ogg"); case LIBMTP_FILETYPE_AUDIBLE: return QLatin1String(""); case LIBMTP_FILETYPE_MP4: return QLatin1String("audio/mp4"); case LIBMTP_FILETYPE_UNDEF_AUDIO: return QLatin1String(""); case LIBMTP_FILETYPE_WMV: return QLatin1String("video/x-ms-wmv"); case LIBMTP_FILETYPE_AVI: return QLatin1String("video/x-msvideo"); case LIBMTP_FILETYPE_MPEG: return QLatin1String("video/mpeg"); case LIBMTP_FILETYPE_ASF: return QLatin1String("video/x-ms-asf"); case LIBMTP_FILETYPE_QT: return QLatin1String("video/quicktime"); case LIBMTP_FILETYPE_UNDEF_VIDEO: return QLatin1String(""); case LIBMTP_FILETYPE_JPEG: return QLatin1String("image/jpeg"); case LIBMTP_FILETYPE_JFIF: return QLatin1String(""); case LIBMTP_FILETYPE_TIFF: return QLatin1String("image/tiff"); case LIBMTP_FILETYPE_BMP: return QLatin1String("image/bmp"); case LIBMTP_FILETYPE_GIF: return QLatin1String("image/gif"); case LIBMTP_FILETYPE_PICT: return QLatin1String("image/x-pict"); case LIBMTP_FILETYPE_PNG: return QLatin1String("image/png"); case LIBMTP_FILETYPE_VCALENDAR1: return QLatin1String("text/x-vcalendar"); case LIBMTP_FILETYPE_VCALENDAR2: return QLatin1String("text/x-vcalendar"); case LIBMTP_FILETYPE_VCARD2: return QLatin1String("text/x-vcard"); case LIBMTP_FILETYPE_VCARD3: return QLatin1String("text/x-vcard"); case LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT: return QLatin1String("image/x-wmf"); case LIBMTP_FILETYPE_WINEXEC: return QLatin1String("application/x-ms-dos-executable"); case LIBMTP_FILETYPE_TEXT: return QLatin1String("text/plain"); case LIBMTP_FILETYPE_HTML: return QLatin1String("text/html"); case LIBMTP_FILETYPE_FIRMWARE: return QLatin1String(""); case LIBMTP_FILETYPE_AAC: return QLatin1String("audio/aac"); case LIBMTP_FILETYPE_MEDIACARD: return QLatin1String(""); case LIBMTP_FILETYPE_FLAC: return QLatin1String("audio/flac"); case LIBMTP_FILETYPE_MP2: return QLatin1String("video/mpeg"); case LIBMTP_FILETYPE_M4A: return QLatin1String("audio/mp4"); case LIBMTP_FILETYPE_DOC: return QLatin1String("application/msword"); case LIBMTP_FILETYPE_XML: return QLatin1String("text/xml"); case LIBMTP_FILETYPE_XLS: return QLatin1String("application/vnd.ms-excel"); case LIBMTP_FILETYPE_PPT: return QLatin1String("application/vnd.ms-powerpoint"); case LIBMTP_FILETYPE_MHT: return QLatin1String(""); case LIBMTP_FILETYPE_JP2: return QLatin1String("image/jpeg2000"); case LIBMTP_FILETYPE_JPX: return QLatin1String("application/x-jbuilder-project"); case LIBMTP_FILETYPE_UNKNOWN: return QLatin1String(""); default: return QLatin1String(""); } } LIBMTP_filetype_t getFiletype(const QString &filename) { LIBMTP_filetype_t filetype; QString ptype = filename.split(QLatin1Char('.')).last(); /* This need to be kept constantly updated as new file types arrive. */ if (ptype == QLatin1String("wav")) { filetype = LIBMTP_FILETYPE_WAV; } else if (ptype == QLatin1String("mp3")) { filetype = LIBMTP_FILETYPE_MP3; } else if (ptype == QLatin1String("wma")) { filetype = LIBMTP_FILETYPE_WMA; } else if (ptype == QLatin1String("ogg")) { filetype = LIBMTP_FILETYPE_OGG; } else if (ptype == QLatin1String("mp4")) { filetype = LIBMTP_FILETYPE_MP4; } else if (ptype == QLatin1String("wmv")) { filetype = LIBMTP_FILETYPE_WMV; } else if (ptype == QLatin1String("avi")) { filetype = LIBMTP_FILETYPE_AVI; } else if (ptype == QLatin1String("mpeg") || ptype == QLatin1String("mpg")) { filetype = LIBMTP_FILETYPE_MPEG; } else if (ptype == QLatin1String("asf")) { filetype = LIBMTP_FILETYPE_ASF; } else if (ptype == QLatin1String("qt") || ptype == QLatin1String("mov")) { filetype = LIBMTP_FILETYPE_QT; } else if (ptype == QLatin1String("wma")) { filetype = LIBMTP_FILETYPE_WMA; } else if (ptype == QLatin1String("jpg") || ptype == QLatin1String("jpeg")) { filetype = LIBMTP_FILETYPE_JPEG; } else if (ptype == QLatin1String("jfif")) { filetype = LIBMTP_FILETYPE_JFIF; } else if (ptype == QLatin1String("tif") || ptype == QLatin1String("tiff")) { filetype = LIBMTP_FILETYPE_TIFF; } else if (ptype == QLatin1String("bmp")) { filetype = LIBMTP_FILETYPE_BMP; } else if (ptype == QLatin1String("gif")) { filetype = LIBMTP_FILETYPE_GIF; } else if (ptype == QLatin1String("pic") || ptype == QLatin1String("pict")) { filetype = LIBMTP_FILETYPE_PICT; } else if (ptype == QLatin1String("png")) { filetype = LIBMTP_FILETYPE_PNG; } else if (ptype == QLatin1String("wmf")) { filetype = LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT; } else if (ptype == QLatin1String("ics")) { filetype = LIBMTP_FILETYPE_VCALENDAR2; } else if (ptype == QLatin1String("exe") || ptype == QLatin1String("com") || ptype == QLatin1String("bat") || ptype == QLatin1String("dll") || ptype == QLatin1String("sys")) { filetype = LIBMTP_FILETYPE_WINEXEC; } else if (ptype == QLatin1String("aac")) { filetype = LIBMTP_FILETYPE_AAC; } else if (ptype == QLatin1String("mp2")) { filetype = LIBMTP_FILETYPE_MP2; } else if (ptype == QLatin1String("flac")) { filetype = LIBMTP_FILETYPE_FLAC; } else if (ptype == QLatin1String("m4a")) { filetype = LIBMTP_FILETYPE_M4A; } else if (ptype == QLatin1String("doc")) { filetype = LIBMTP_FILETYPE_DOC; } else if (ptype == QLatin1String("xml")) { filetype = LIBMTP_FILETYPE_XML; } else if (ptype == QLatin1String("xls")) { filetype = LIBMTP_FILETYPE_XLS; } else if (ptype == QLatin1String("ppt")) { filetype = LIBMTP_FILETYPE_PPT; } else if (ptype == QLatin1String("mht")) { filetype = LIBMTP_FILETYPE_MHT; } else if (ptype == QLatin1String("jp2")) { filetype = LIBMTP_FILETYPE_JP2; } else if (ptype == QLatin1String("jpx")) { filetype = LIBMTP_FILETYPE_JPX; } else if (ptype == QLatin1String("bin")) { filetype = LIBMTP_FILETYPE_FIRMWARE; } else if (ptype == QLatin1String("vcf")) { filetype = LIBMTP_FILETYPE_VCARD3; } else { /* Tagging as unknown file type */ filetype = LIBMTP_FILETYPE_UNKNOWN; } return filetype; } QMap getDevicestorages(LIBMTP_mtpdevice_t *&device) { - qCDebug(LOG_KIO_MTP) << "[ENTER]" << (device == 0); + qCDebug(LOG_KIO_MTP) << "[ENTER]" << (device == nullptr); QMap storages; if (device) { - for (LIBMTP_devicestorage_t *storage = device->storage; storage != NULL; storage = storage->next) { + for (LIBMTP_devicestorage_t *storage = device->storage; storage != nullptr; storage = storage->next) { // char *storageIdentifier = storage->VolumeIdentifier; char *storageDescription = storage->StorageDescription; QString storagename; // if ( !storageIdentifier ) storagename = QString::fromUtf8(storageDescription); // else // storagename = QString::fromUtf8 ( storageIdentifier ); qCDebug(LOG_KIO_MTP) << "found storage" << storagename; storages.insert(storagename, storage); } } qCDebug(LOG_KIO_MTP) << "[EXIT]" << storages.size(); return storages; } QMap getFiles(LIBMTP_mtpdevice_t *&device, uint32_t storage_id, uint32_t parent_id) { qCDebug(LOG_KIO_MTP) << "getFiles() for parent" << parent_id; QMap fileMap; LIBMTP_file_t *files = LIBMTP_Get_Files_And_Folders(device, storage_id, parent_id), *file; - for (file = files; file != NULL; file = file->next) { + for (file = files; file != nullptr; file = file->next) { fileMap.insert(QString::fromUtf8(file->filename), file); // qCDebug(LOG_KIO_MTP) << "found file" << file->filename; } qCDebug(LOG_KIO_MTP) << "[EXIT]"; return fileMap; } void getEntry(UDSEntry &entry, LIBMTP_mtpdevice_t *device) { char *charName = LIBMTP_Get_Friendlyname(device); char *charModel = LIBMTP_Get_Modelname(device); // prefer friendly devicename over model QString deviceName; if (!charName) { deviceName = QString::fromUtf8(charModel); } else { deviceName = QString::fromUtf8(charName); } entry.insert(UDSEntry::UDS_NAME, deviceName); entry.insert(UDSEntry::UDS_ICON_NAME, QLatin1String("multimedia-player")); entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); } void getEntry(UDSEntry &entry, const LIBMTP_devicestorage_t *storage) { // char *charIdentifier = storage->VolumeIdentifier; char *charDescription = storage->StorageDescription; QString storageName; // if ( !charIdentifier ) storageName = QString::fromUtf8(charDescription); // else // storageName = QString::fromUtf8 ( charIdentifier ); entry.insert(UDSEntry::UDS_NAME, storageName); entry.insert(UDSEntry::UDS_ICON_NAME, QLatin1String("drive-removable-media")); entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); } void getEntry(UDSEntry &entry, const LIBMTP_file_t *file) { entry.insert(UDSEntry::UDS_NAME, QString::fromUtf8(file->filename)); if (file->filetype == LIBMTP_FILETYPE_FOLDER) { entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); entry.insert(UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); } else { entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); entry.insert(UDSEntry::UDS_SIZE, file->filesize); entry.insert(UDSEntry::UDS_MIME_TYPE, getMimetype(file->filetype)); } entry.insert(UDSEntry::UDS_INODE, file->item_id); entry.insert(UDSEntry::UDS_ACCESS_TIME, file->modificationdate); entry.insert(UDSEntry::UDS_MODIFICATION_TIME, file->modificationdate); entry.insert(UDSEntry::UDS_CREATION_TIME, file->modificationdate); } diff --git a/network/ioslave/networkdbusinterface.h b/network/ioslave/networkdbusinterface.h index e5e04aeb..0936f560 100644 --- a/network/ioslave/networkdbusinterface.h +++ b/network/ioslave/networkdbusinterface.h @@ -1,74 +1,74 @@ /* This file is part of the network kioslave, part of the KDE project. Copyright 2009 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef NETWORKDBUSINTERFACE_H #define NETWORKDBUSINTERFACE_H // network #include // Qt #include #include // TODO: see file networkdbus.h class NetworkDBusInterface: public QDBusAbstractInterface { Q_OBJECT public: - NetworkDBusInterface( const QString& service, const QString& path, const QDBusConnection& connection, QObject* parent = 0 ); + NetworkDBusInterface( const QString& service, const QString& path, const QDBusConnection& connection, QObject* parent = nullptr ); virtual ~NetworkDBusInterface(); public Q_SLOTS: QDBusReply deviceData( const QString& hostAddress ); QDBusReply serviceData( const QString& hostAddress, const QString& serviceName, const QString& serviceType ); QDBusReply deviceDataList(); QDBusReply serviceDataList( const QString& hostAddress ); }; // TODO: is QDBus::Block the right solution here? inline QDBusReply NetworkDBusInterface::deviceData( const QString& hostAddress ) { QList argumentList; argumentList << qVariantFromValue(hostAddress); return callWithArgumentList( QDBus::Block, QString::fromLatin1("deviceData"), argumentList ); } inline QDBusReply NetworkDBusInterface::serviceData( const QString& hostAddress, const QString& serviceName, const QString& serviceType ) { QList argumentList; argumentList << qVariantFromValue(hostAddress) << qVariantFromValue(serviceName) << qVariantFromValue(serviceType); return callWithArgumentList( QDBus::Block, QString::fromLatin1("serviceData"), argumentList ); } inline QDBusReply NetworkDBusInterface::deviceDataList() { return call( QString::fromLatin1("deviceDataList") ); } inline QDBusReply NetworkDBusInterface::serviceDataList( const QString& hostAddress ) { QList argumentList; argumentList << qVariantFromValue(hostAddress); return callWithArgumentList( QDBus::Block, QString::fromLatin1("serviceDataList"), argumentList ); } #endif diff --git a/network/ioslave/networkthread.cpp b/network/ioslave/networkthread.cpp index 02e729f4..85e16ec5 100644 --- a/network/ioslave/networkthread.cpp +++ b/network/ioslave/networkthread.cpp @@ -1,92 +1,92 @@ /* This file is part of the network kioslave, part of the KDE project. Copyright 2009 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "networkthread.h" // ioslave #include "networkinitwatcher.h" // network #include "network.h" #include "netdevice.h" #include "netservice.h" //Qt #include #include NetworkThread::NetworkThread() : QThread() - , mNetwork( 0 ) + , mNetwork( nullptr ) , mContinue( true ) { } Mollet::Network* NetworkThread::network() const { return mNetwork; } void NetworkThread::pause() { //qDebug()<<"before lock"; mMutex.lock(); //qDebug()<<"after lock"; exit(); //qDebug()<<"after exit"; } void NetworkThread::unpause() { //qDebug()<<"before unlock"; mMutex.unlock(); //qDebug()<<"after unlock"; } void NetworkThread::finish() { mContinue = false; exit(); } void NetworkThread::run() { mNetwork = Mollet::Network::network(); //qDebug()<<"starting with lock"; mMutex.lock(); new NetworkInitWatcher( mNetwork, &mMutex ); do { //qDebug()<<"going exec()"; exec(); //qDebug()<<"left exec()"; mMutex.lock(); //qDebug()<<"after lock"; mMutex.unlock(); //qDebug()<<"after unlock"; } while( mContinue ); } NetworkThread::~NetworkThread() { } diff --git a/network/kded/kioslavenotifier.h b/network/kded/kioslavenotifier.h index df052aed..d212d7dd 100644 --- a/network/kded/kioslavenotifier.h +++ b/network/kded/kioslavenotifier.h @@ -1,69 +1,69 @@ /* This file is part of the network kioslave, part of the KDE project. Copyright 2009 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KIOSLAVENOTIFIER_H #define KIOSLAVENOTIFIER_H // Qt #include #include namespace Mollet { class Network; class NetDevice; class NetService; class KioSlaveNotifier : public QObject { Q_OBJECT public: - explicit KioSlaveNotifier( Network* network, QObject* parent = 0 ); + explicit KioSlaveNotifier( Network* network, QObject* parent = nullptr ); virtual ~KioSlaveNotifier(); public: // for debugging, remove also from adaptor.xml QStringList watchedDirectories() const; public Q_SLOTS: void onDirectoryEntered( const QString& directory ); void onDirectoryLeft( const QString& directory ); private: void notifyAboutAdded( const QString& dirId ); void notifyAboutRemoved( const QString& dirId, const QString& itemPath ); private Q_SLOTS: void onDevicesAdded( const QList& deviceList ); void onDevicesRemoved( const QList& deviceList ); void onServicesAdded( const QList& serviceList ); void onServicesRemoved( const QList& serviceList ); private: QHash mWatchedDirs; }; } #endif diff --git a/network/network/builder/dnssd/dnssdnetworkbuilder.cpp b/network/network/builder/dnssd/dnssdnetworkbuilder.cpp index 8ae508e7..14920e77 100644 --- a/network/network/builder/dnssd/dnssdnetworkbuilder.cpp +++ b/network/network/builder/dnssd/dnssdnetworkbuilder.cpp @@ -1,321 +1,321 @@ /* This file is part of the Mollet network library, part of the KDE project. Copyright 2009-2010 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "dnssdnetworkbuilder.h" // lib #include "dnssdnetsystemable.h" #include "abstractnetsystemfactory.h" #include "network_p.h" #include "netdevice_p.h" // KDE #include #include // Qt #include #include #include namespace Mollet { DNSSDNetworkBuilder::DNSSDNetworkBuilder( NetworkPrivate* networkPrivate ) : AbstractNetworkBuilder() , mNetworkPrivate( networkPrivate ) - , mServiceTypeBrowser( 0 ) + , mServiceTypeBrowser( nullptr ) { } void DNSSDNetworkBuilder::registerNetSystemFactory( AbstractNetSystemFactory* netSystemFactory ) { DNSSDNetSystemAble* dnssdNetSystemAble = qobject_cast( netSystemFactory ); if( dnssdNetSystemAble ) mNetSystemFactoryList.append( dnssdNetSystemAble ); } void DNSSDNetworkBuilder::start() { mIsInit = true; mNoOfInitServiceTypes = 0; mServiceTypeBrowser = new KDNSSD::ServiceTypeBrowser(); connect(mServiceTypeBrowser, &KDNSSD::ServiceTypeBrowser::serviceTypeAdded, this, &DNSSDNetworkBuilder::addServiceType); connect(mServiceTypeBrowser, &KDNSSD::ServiceTypeBrowser::serviceTypeRemoved, this, &DNSSDNetworkBuilder::removeServiceType); connect(mServiceTypeBrowser, &KDNSSD::ServiceTypeBrowser::finished, this, &DNSSDNetworkBuilder::onServiceTypeBrowserFinished); // TODO: add a signal network initialized to Network, so is cleared when first usable mServiceTypeBrowser->startBrowse(); } void DNSSDNetworkBuilder::addServiceType( const QString& serviceType ) { //qDebug()<startBrowse(); } void DNSSDNetworkBuilder::removeServiceType( const QString& serviceType ) { //qDebug()<::Iterator it = mServiceBrowserTable.find( serviceType ); if( it == mServiceBrowserTable.end() ) return; KDNSSD::ServiceBrowser* serviceBrowser = *it; mServiceBrowserTable.erase( it ); // TODO: will all servicesRemoved be called before? on test NO! serviceBrowser->deleteLater(); #endif } void DNSSDNetworkBuilder::addService( KDNSSD::RemoteService::Ptr service ) { QList& deviceList = mNetworkPrivate->deviceList(); QString hostName = service->hostName(); // TODO: this blocks. and the ip address should be delivered from DNS-SD with resolve const QHostAddress hostAddress = KDNSSD::ServiceBrowser::resolveHostName( hostName ); const QString ipAddress = hostAddress.toString(); // forget domain name if just ip address if( hostName == ipAddress ) hostName.clear(); // device TODO: only search for if we can create the service? - NetDevicePrivate* d = 0; - const NetDevice* deviceOfService = 0; + NetDevicePrivate* d = nullptr; + const NetDevice* deviceOfService = nullptr; foreach( const NetDevice& device, deviceList ) { const QString deviceHostName = device.hostName(); const bool useIpAddress = ( deviceHostName.isEmpty() || hostName.isEmpty() ); const bool isSameAddress = useIpAddress ? ( device.ipAddress() == ipAddress ) : ( deviceHostName == hostName ); //qDebug()<<"existing device:"<serviceName() is fragile, but matches // current approach in removeService(...) QString id; const QString serviceType = service->type(); foreach( const DNSSDNetSystemAble* factory, mNetSystemFactoryList ) { if( factory->canCreateNetSystemFromDNSSD(serviceType) ) { id = factory->dnssdId( service ); break; } } if( d->hasService(id) ) return; deviceOfService = &device; break; } } if( !d ) { const QString deviceName = hostName.left( hostName.indexOf(QLatin1Char('.')) ); d = new NetDevicePrivate( deviceName ); d->setHostName( hostName ); d->setIpAddress( ipAddress ); NetDevice device( d ); deviceList.append( device ); deviceOfService = &deviceList.last(); QList newDevices; newDevices.append( device ); // TODO: the new service will be announced two times, once with the new device and once alone. // what to do about that? which order? okay? for now just do not attach services before. find usecases. mNetworkPrivate->emitDevicesAdded( newDevices ); //qDebug()<<"new device:"<type(); } else { if( d->hostName().isEmpty() && ! hostName.isEmpty() ) d->setHostName( hostName ); } const QString serviceType = service->type(); - NetServicePrivate* netServicePrivate = 0; + NetServicePrivate* netServicePrivate = nullptr; // do a priority based lookup who can build the object // TODO: priorisation foreach( const DNSSDNetSystemAble* factory, mNetSystemFactoryList ) { if( factory->canCreateNetSystemFromDNSSD(serviceType) ) { // TODO: here we should rather see if this service already exists netServicePrivate = factory->createNetService( service, *deviceOfService ); break; } } // TODO: create dummy service // if( ! netServicePrivate ) // netServicePrivate = new UnknownService; NetService netService( netServicePrivate ); d->addService( netService ); // try guessing the device type by the services on it // TODO: move into devicefactory NetDevice::Type deviceTypeByService = NetDevice::Unknown; QString deviceName; if( serviceType == QLatin1String("_workstation._tcp") ) { deviceTypeByService = NetDevice::Workstation; deviceName = service->serviceName().left( service->serviceName().lastIndexOf(QLatin1Char('[')) ).trimmed(); } else if( serviceType == QLatin1String("_net-assistant._udp") ) { deviceTypeByService = NetDevice::Workstation; deviceName = service->serviceName(); } else if( serviceType == QLatin1String("_airport._tcp") ) deviceTypeByService = NetDevice::Router; else if( serviceType == QLatin1String("_ipp._tcp") || serviceType == QLatin1String("_printer._tcp") || serviceType == QLatin1String("_pdl-datastream._tcp") ) { deviceTypeByService = NetDevice::Printer; deviceName = service->serviceName(); } if( deviceTypeByService != NetDevice::Unknown ) { if( deviceTypeByService > d->type() ) { d->setType( deviceTypeByService ); if( ! deviceName.isEmpty() ) d->setName( deviceName ); } } QList newServices; newServices.append( netService ); mNetworkPrivate->emitServicesAdded( newServices ); } // TODO: each builder should refcount its services, so a shared one does not get removed to early // (might disappear for one sd because of master breakdown) void DNSSDNetworkBuilder::removeService( KDNSSD::RemoteService::Ptr service ) { QList& deviceList = mNetworkPrivate->deviceList(); const QString hostName = service->hostName(); // device QMutableListIterator it( deviceList ); while( it.hasNext()) { const NetDevice& device = it.next(); if( device.hostName() == hostName ) { // //qDebug()<type(); foreach( const DNSSDNetSystemAble* factory, mNetSystemFactoryList ) { if( factory->canCreateNetSystemFromDNSSD(serviceType) ) { id = factory->dnssdId( service ); break; } } NetDevicePrivate* d = device.dPtr(); NetService netService = d->removeService( id ); if( !netService.isValid() ) break; QList removedServices; removedServices.append( netService ); mNetworkPrivate->emitServicesRemoved( removedServices ); // remove device on last service if( d->serviceList().count() == 0 ) { QList removedDevices; removedDevices.append( device ); // remove only after taking copy from reference into removed list it.remove(); mNetworkPrivate->emitDevicesRemoved( removedDevices ); } break; } } } void DNSSDNetworkBuilder::onServiceTypeBrowserFinished() { // //qDebug(); if( mIsInit ) { mIsInit = false; if( mNoOfInitServiceTypes == 0 ) emit initDone(); } } void DNSSDNetworkBuilder::onServiceBrowserFinished() { --mNoOfInitServiceTypes; // //qDebug()<<"mIsInit="< This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "simpleitemfactory.h" // lib #include "netservice_p.h" // #include "service.h" #include "upnp/cagibidevice.h" // Qt #include // KDE #include #include namespace Mollet { struct DNSSDServiceDatum { const char* dnssdTypeName; const char* typeName; const char* fallbackIconName; bool isFilesystem; const char* protocol; // KDE can forward to it const char* pathField; const char* iconField; const char* userField; const char* passwordField; void feedUrl( QUrl* url, const KDNSSD::RemoteService* remoteService ) const; }; static const DNSSDServiceDatum DNSSDServiceData[] = { // file services - { "_ftp._tcp", "ftp", "folder-remote", true, "ftp", "path", 0, "u", "p" }, - { "_sftp-ssh._tcp", "sftp-ssh", "folder-remote", true, "sftp", 0, 0, "u", "p" }, - { "_ftps._tcp", "ftps", "folder-remote", true, "ftps", "path", 0, "u", "p" }, - { "_nfs._tcp", "nfs", "folder-remote", true, "nfs", "path", 0, 0, 0 }, - { "_afpovertcp._tcp", "afpovertcp", "folder-remote", true, "afp", "path", 0, 0, 0 }, - { "_smb._tcp", "smb", "folder-remote", true, "smb", "path", 0, "u", "p" }, - { "_webdav._tcp", "webdav", "folder-remote", true, "webdav", "path", 0, "u", "p" }, - { "_webdavs._tcp", "webdavs", "folder-remote", true, "webdavs", "path", 0, "u", "p" }, - - { "_svn._tcp", "svn", "folder-sync", true, 0, 0, 0, 0, 0 }, - { "_rsync._tcp", "rsync", "folder-sync", true, 0, 0, 0, 0, 0 }, + { "_ftp._tcp", "ftp", "folder-remote", true, "ftp", "path", nullptr, "u", "p" }, + { "_sftp-ssh._tcp", "sftp-ssh", "folder-remote", true, "sftp", nullptr, nullptr, "u", "p" }, + { "_ftps._tcp", "ftps", "folder-remote", true, "ftps", "path", nullptr, "u", "p" }, + { "_nfs._tcp", "nfs", "folder-remote", true, "nfs", "path", nullptr, nullptr, nullptr }, + { "_afpovertcp._tcp", "afpovertcp", "folder-remote", true, "afp", "path", nullptr, nullptr, nullptr }, + { "_smb._tcp", "smb", "folder-remote", true, "smb", "path", nullptr, "u", "p" }, + { "_webdav._tcp", "webdav", "folder-remote", true, "webdav", "path", nullptr, "u", "p" }, + { "_webdavs._tcp", "webdavs", "folder-remote", true, "webdavs", "path", nullptr, "u", "p" }, + + { "_svn._tcp", "svn", "folder-sync", true, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_rsync._tcp", "rsync", "folder-sync", true, nullptr, nullptr, nullptr, nullptr, nullptr }, // email - { "_imap._tcp", "imap", "email", false, 0, 0, 0, 0, 0 }, - { "_pop3._tcp", "pop3", "email", false, "pop3", 0, 0, 0, 0 }, + { "_imap._tcp", "imap", "email", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pop3._tcp", "pop3", "email", false, "pop3", nullptr, nullptr, nullptr, nullptr }, // shell services - { "_ssh._tcp", "ssh", "terminal", false, "ssh", 0, 0, "u", "p" }, - { "_telnet._tcp", "telnet", "terminal", false, "telnet", 0, 0, "u", "p" }, - { "_rfb._tcp", "rfb", "krfb", false, "vnc", "path", 0, "u", "p" }, - { "_rdp._tcp", "rdp", "krfb", false, "rdp", 0, 0, 0, 0 }, + { "_ssh._tcp", "ssh", "terminal", false, "ssh", nullptr, nullptr, "u", "p" }, + { "_telnet._tcp", "telnet", "terminal", false, "telnet", nullptr, nullptr, "u", "p" }, + { "_rfb._tcp", "rfb", "krfb", false, "vnc", "path", nullptr, "u", "p" }, + { "_rdp._tcp", "rdp", "krfb", false, "rdp", nullptr, nullptr, nullptr, nullptr }, // other standard services - { "_http._tcp", "http", "folder-html", false, "http", "path", 0, "u", "p" }, - { "_ntp._udp", "ntp", "xclock", false, 0, 0, 0, 0, 0 }, - { "_ldap._tcp", "ldap", "user-group-properties", false, "ldap", 0, 0, 0, 0 }, + { "_http._tcp", "http", "folder-html", false, "http", "path", nullptr, "u", "p" }, + { "_ntp._udp", "ntp", "xclock", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_ldap._tcp", "ldap", "user-group-properties", false, "ldap", nullptr, nullptr, nullptr, nullptr }, // user2user (chat, collaboration) - { "_xmpp-server._tcp", "xmpp-server", "xchat", false, "jabber", 0, 0, 0, 0 }, - { "_presence._tcp", "presence", "im-user", false, "presence", 0, 0, 0, 0 }, - { "_lobby._tcp", "lobby", "document-edit", false, 0, 0, 0, 0, 0 }, - { "_giver._tcp", "giver", "folder-remote", false, 0, 0, 0, 0, 0 }, - { "_sip._udp", "sip", "phone", false, 0, 0, 0, 0, 0 }, - { "_h323._tcp", "h323", "phone", false, 0, 0, 0, 0, 0 }, - { "_skype._tcp", "skype", "phone", false, 0, 0, 0, 0, 0 }, + { "_xmpp-server._tcp", "xmpp-server", "xchat", false, "jabber", nullptr, nullptr, nullptr, nullptr }, + { "_presence._tcp", "presence", "im-user", false, "presence", nullptr, nullptr, nullptr, nullptr }, + { "_lobby._tcp", "lobby", "document-edit", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_giver._tcp", "giver", "folder-remote", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_sip._udp", "sip", "phone", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_h323._tcp", "h323", "phone", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_skype._tcp", "skype", "phone", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // printing - { "_ipp._tcp", "ipp", "printer", false, "ipp", "path", 0, "u", "p" }, - { "_printer._tcp", "printer", "printer", false, 0, 0, 0, 0, 0 }, - { "_pdl-datastream._tcp", "pdl-datastream", "printer", false, 0, 0, 0, 0, 0 }, + { "_ipp._tcp", "ipp", "printer", false, "ipp", "path", nullptr, "u", "p" }, + { "_printer._tcp", "printer", "printer", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pdl-datastream._tcp", "pdl-datastream", "printer", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // KDE workspace - { "_plasma._tcp", "plasma", "plasma", false, "plasma", "name", "icon", 0, 0 }, + { "_plasma._tcp", "plasma", "plasma", false, "plasma", "name", "icon", nullptr, nullptr }, // KDE games - { "_kbattleship._tcp", "kbattleship", "kbattleship", false, "kbattleship", 0, 0, 0, 0 }, - { "_lskat._tcp", "lskat", "lskat", false, 0, 0, 0, 0, 0 }, - { "_kfourinline._tcp", "kfourinline", "kfourinline", false, 0, 0, 0, 0, 0 }, - { "_ksirk._tcp", "ksirk", "ksirk", false, 0, 0, 0, 0, 0 }, + { "_kbattleship._tcp", "kbattleship", "kbattleship", false, "kbattleship", nullptr, nullptr, nullptr, nullptr }, + { "_lskat._tcp", "lskat", "lskat", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_kfourinline._tcp", "kfourinline", "kfourinline", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_ksirk._tcp", "ksirk", "ksirk", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // hardware - { "_pulse-server._tcp","pulse-server","audio-card", false, 0, 0, 0, 0, 0 }, - { "_pulse-source._tcp","pulse-source","audio-input-line", false, 0, 0, 0, 0, 0 }, - { "_pulse-sink._tcp", "pulse-sink", "speaker", false, 0, 0, 0, 0, 0 }, - { "_udisks-ssh._tcp", "udisks-ssh", "drive-harddisk", false, 0, 0, 0, 0, 0 }, - { "_libvirt._tcp", "libvirt", "computer", false, 0, 0, 0, 0, 0 }, - { "_airmouse._tcp", "airmouse", "input-mouse", false, 0, 0, 0, 0, 0 }, + { "_pulse-server._tcp","pulse-server","audio-card", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pulse-source._tcp","pulse-source","audio-input-line", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pulse-sink._tcp", "pulse-sink", "speaker", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_udisks-ssh._tcp", "udisks-ssh", "drive-harddisk", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_libvirt._tcp", "libvirt", "computer", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_airmouse._tcp", "airmouse", "input-mouse", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // database - { "_postgresql._tcp", "postgresql", "server-database", false, 0, 0, 0, 0, 0 }, - { "_couchdb_location._tcp", "couchdb_location", "server-database", false, 0, 0, 0, 0, 0 }, + { "_postgresql._tcp", "postgresql", "server-database", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_couchdb_location._tcp", "couchdb_location", "server-database", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // else - { "_realplayfavs._tcp","realplayfavs","favorites", false, 0, 0, 0, 0, 0 }, - { "_acrobatSRV._tcp", "acrobat-server","application-pdf", false, 0, 0, 0, 0, 0 }, - { "_adobe-vc._tcp", "adobe-vc", "services", false, 0, 0, 0, 0, 0 }, - { "_ggz._tcp", "ggz", "applications-games", false, "ggz", 0, 0, 0, 0 }, - - { "_pgpkey-ldap._tcp", "pgpkey-ldap", "application-pgp-keys", false, 0, 0, 0, 0, 0 }, - { "_pgpkey-hkp._tcp", "pgpkey-hkp", "application-pgp-keys", false, 0, 0, 0, 0, 0 }, - { "_pgpkey-https._tcp", "pgpkey-https", "application-pgp-keys", true, "https", "path", 0, 0, 0 }, + { "_realplayfavs._tcp","realplayfavs","favorites", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_acrobatSRV._tcp", "acrobat-server","application-pdf", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_adobe-vc._tcp", "adobe-vc", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_ggz._tcp", "ggz", "applications-games", false, "ggz", nullptr, nullptr, nullptr, nullptr }, + + { "_pgpkey-ldap._tcp", "pgpkey-ldap", "application-pgp-keys", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pgpkey-hkp._tcp", "pgpkey-hkp", "application-pgp-keys", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_pgpkey-https._tcp", "pgpkey-https", "application-pgp-keys", true, "https", "path", nullptr, nullptr, nullptr }, // Maemo - { "_maemo-inf._tcp", "maemo-inf", "pda", false, 0, 0, 0, 0, 0 }, + { "_maemo-inf._tcp", "maemo-inf", "pda", false, nullptr, nullptr, nullptr, nullptr, nullptr }, // TODO: _maemo-inf._tcp seems to be not a service, just some about info, use to identify device and hide // Apple - { "_airport._tcp", "airport", "network-wireless", false, 0, 0, 0, 0, 0 }, - { "_daap._tcp", "daap", "folder-sound", false, 0, 0, 0, 0, 0 }, - { "_dacp._tcp", "dacp", "folder-sound", false, 0, 0, 0, 0, 0 }, - { "_eppc._tcp", "eppc", "network-connect", false, 0, 0, 0, 0, 0 }, - { "_net-assistant._udp", "net-assistant", "services", false, 0, 0, 0, 0, 0 }, - { "_odisk._tcp", "odisk", "media-optical", false, 0, 0, 0, 0, 0 }, - { "_raop._tcp", "raop", "speaker", false, 0, 0, 0, 0, 0 }, - { "_touch-able._tcp", "touch-able", "input-tablet", false, 0, 0, 0, 0, 0 }, - { "_workstation._tcp", "workstation", "network-workgroup", false, 0, 0, 0, 0, 0 }, - { "_sleep-proxy._udp", "sleep-proxy", "services", false, 0, 0, 0, 0, 0 }, - { "_nssocketport._tcp", "nssocketport", "services", false, 0, 0, 0, 0, 0 }, - { "_home-sharing._tcp", "home-sharing", "services", false, 0, 0, 0, 0, 0 }, - { "_appletv-itunes._tcp","appletv-itunes","services", false, 0, 0, 0, 0, 0 }, - { "_appletv-pair._tcp", "appletv-pair", "services", false, 0, 0, 0, 0, 0 } + { "_airport._tcp", "airport", "network-wireless", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_daap._tcp", "daap", "folder-sound", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_dacp._tcp", "dacp", "folder-sound", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_eppc._tcp", "eppc", "network-connect", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_net-assistant._udp", "net-assistant", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_odisk._tcp", "odisk", "media-optical", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_raop._tcp", "raop", "speaker", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_touch-able._tcp", "touch-able", "input-tablet", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_workstation._tcp", "workstation", "network-workgroup", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_sleep-proxy._udp", "sleep-proxy", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_nssocketport._tcp", "nssocketport", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_home-sharing._tcp", "home-sharing", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_appletv-itunes._tcp","appletv-itunes","services", false, nullptr, nullptr, nullptr, nullptr, nullptr }, + { "_appletv-pair._tcp", "appletv-pair", "services", false, nullptr, nullptr, nullptr, nullptr, nullptr } }; // result["_ssh._tcp"]= DNSSDNetServiceBuilder(i18n("Remote disk (fish)"), "fish", "service/ftp", QString(), "u", "p"); // network-server-database icon // see // SubEthaEdit 2 // Defined TXT keys: txtvers=1, name=, userid=, version=2 static const int DNSSDServiceDataSize = sizeof( DNSSDServiceData ) / sizeof( DNSSDServiceData[0] ); -static const DNSSDServiceDatum UnknownServiceDatum = { "", "unknown", "unknown", false, 0, 0, 0, 0, 0 }; +static const DNSSDServiceDatum UnknownServiceDatum = { "", "unknown", "unknown", false, nullptr, nullptr, nullptr, nullptr, nullptr }; // TODO: // * find out how ws (webservices, _ws._tcp), upnp (_upnp._tcp) are exactly meant to be used // * learn about uddi // * see how the apple specific protocols can be used for devicetype identification (printer, scanner) void DNSSDServiceDatum::feedUrl( QUrl* url, const KDNSSD::RemoteService* remoteService ) const { const QMap serviceTextData = remoteService->textData(); url->setScheme( QString::fromLatin1(protocol) ); if( userField ) url->setUserName( QLatin1String(serviceTextData.value(QLatin1String(userField)).constData()) ); if( passwordField ) url->setPassword( QLatin1String(serviceTextData.value(QLatin1String(passwordField)).constData()) ); if( pathField ) url->setPath( QLatin1String(serviceTextData.value(QLatin1String(pathField)).constData()) ); url->setHost( remoteService->hostName() ); url->setPort( remoteService->port() ); } SimpleItemFactory::SimpleItemFactory() : AbstractNetSystemFactory() { } bool SimpleItemFactory::canCreateNetSystemFromDNSSD( const QString& serviceType ) const { Q_UNUSED( serviceType ) return true; } QString SimpleItemFactory::dnssdId( const KDNSSD::RemoteService::Ptr& dnssdService ) const { return dnssdService->type() + QLatin1Char('_') + dnssdService->serviceName(); } NetServicePrivate* SimpleItemFactory::createNetService( const KDNSSD::RemoteService::Ptr& dnssdService, const NetDevice& device ) const { NetServicePrivate* result; const QString dnssdServiceType = dnssdService->type(); const DNSSDServiceDatum* serviceDatum = &UnknownServiceDatum; for( int i = 0; i< DNSSDServiceDataSize; ++i ) { const DNSSDServiceDatum* datum = &DNSSDServiceData[i]; if( dnssdServiceType == QLatin1String(datum->dnssdTypeName) ) { serviceDatum = datum; break; } } QUrl url; if( serviceDatum->protocol ) serviceDatum->feedUrl( &url, dnssdService.data() ); const bool isUnknown = ( serviceDatum == &UnknownServiceDatum ); const QString typeName = isUnknown ? dnssdServiceType.mid( 1, dnssdServiceType.lastIndexOf(QLatin1Char('.'))-1 ) : QString::fromLatin1( serviceDatum->typeName ); QString iconName = QString::fromLatin1(serviceDatum->fallbackIconName); if ( serviceDatum->iconField ) { const QMap serviceTextData = dnssdService->textData(); QString serviceIconName = QString::fromUtf8(serviceTextData[QString::fromLatin1(serviceDatum->iconField)]); if ( QIcon::hasThemeIcon(serviceIconName) ) { iconName = serviceIconName; } } result = new NetServicePrivate( dnssdService->serviceName(), iconName, typeName, device, url.url(), SimpleItemFactory::dnssdId(dnssdService) ); return result; } bool SimpleItemFactory::canCreateNetSystemFromUpnp( const Cagibi::Device& upnpDevice ) const { Q_UNUSED( upnpDevice ) return true; } QString SimpleItemFactory::upnpId( const Cagibi::Device& upnpDevice ) const { return upnpDevice.udn(); } // TODO: add KIcon with specialiced KIconLoader (fetches Icons via D-Bus) NetServicePrivate* SimpleItemFactory::createNetService( const Cagibi::Device& upnpDevice, const NetDevice& device ) const { NetServicePrivate* result; QString url = upnpDevice.presentationUrl(); if( url.isEmpty() ) { url = QString::fromLatin1( "upnp://" ); url.append( upnpDevice.udn() ); } result = new NetServicePrivate( upnpDevice.friendlyName(), QString::fromLatin1("unknown"), QString::fromLatin1("upnp.")+upnpDevice.type(), device, url, SimpleItemFactory::upnpId(upnpDevice) ); return result; } SimpleItemFactory::~SimpleItemFactory() {} } diff --git a/network/network/builder/upnp/upnpnetworkbuilder.cpp b/network/network/builder/upnp/upnpnetworkbuilder.cpp index cc524168..28d20fce 100644 --- a/network/network/builder/upnp/upnpnetworkbuilder.cpp +++ b/network/network/builder/upnp/upnpnetworkbuilder.cpp @@ -1,356 +1,356 @@ /* This file is part of the Mollet network library, part of the KDE project. Copyright 2009-2011 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "upnpnetworkbuilder.h" // lib #include "upnpnetsystemable.h" #include "abstractnetsystemfactory.h" #include "network_p.h" #include "netdevice_p.h" #include "netservice_p.h" #include "cagibidevice.h" #include "cagibidbuscodec.h" // Qt #include #include #include #include #include #include #include #include namespace Mollet { static const char cagibiServiceName[] = "org.kde.Cagibi"; static const char cagibiDeviceListObjectPath[] = "/org/kde/Cagibi/DeviceList"; static const char cagibiDeviceListInterface[] = "org.kde.Cagibi.DeviceList"; UpnpNetworkBuilder::UpnpNetworkBuilder( NetworkPrivate* networkPrivate ) : AbstractNetworkBuilder() , mNetworkPrivate( networkPrivate ) - , mCagibiDeviceListDBusProxy( 0 ) + , mCagibiDeviceListDBusProxy( nullptr ) { } void UpnpNetworkBuilder::registerNetSystemFactory( AbstractNetSystemFactory* netSystemFactory ) { UpnpNetSystemAble* upnpNetSystemAble = qobject_cast( netSystemFactory ); if( upnpNetSystemAble ) mNetSystemFactoryList.append( upnpNetSystemAble ); } void UpnpNetworkBuilder::start() { QMetaObject::invokeMethod( this, "startBrowse", Qt::QueuedConnection ); } void UpnpNetworkBuilder::startBrowse() { qDBusRegisterMetaType(); qDBusRegisterMetaType(); QDBusConnection dbusConnection = QDBusConnection::systemBus(); const QString serviceName = QLatin1String( cagibiServiceName ); const QString deviceListObjectPath = QLatin1String( cagibiDeviceListObjectPath ); const QString deviceListInterface = QLatin1String( cagibiDeviceListInterface ); // install service watcher QDBusServiceWatcher* cagibiServiceWatcher = new QDBusServiceWatcher( serviceName, dbusConnection, QDBusServiceWatcher::WatchForOwnerChange, this ); connect( cagibiServiceWatcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), SLOT(onCagibiServiceOwnerChanged(QString,QString,QString)) ); // create devicelist proxy mCagibiDeviceListDBusProxy = new QDBusInterface( serviceName, deviceListObjectPath, deviceListInterface, dbusConnection, this ); connect( mCagibiDeviceListDBusProxy, SIGNAL(devicesAdded(DeviceTypeMap)), SLOT(onDevicesAdded(DeviceTypeMap)) ); connect( mCagibiDeviceListDBusProxy, SIGNAL(devicesRemoved(DeviceTypeMap)), SLOT(onDevicesRemoved(DeviceTypeMap)) ); // query current devicelist queryCurrentDevices(); emit initDone(); } void UpnpNetworkBuilder::queryCurrentDevices() { // query current devicelist QDBusPendingCall allDevicesCall = mCagibiDeviceListDBusProxy->asyncCall( QLatin1String("allDevices") ); QDBusPendingCallWatcher* allDevicesCallWatcher = new QDBusPendingCallWatcher( allDevicesCall, this ); connect( allDevicesCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onAllDevicesCallFinished(QDBusPendingCallWatcher*)) ); } void UpnpNetworkBuilder::onAllDevicesCallFinished( QDBusPendingCallWatcher* allDevicesCallWatcher ) { QDBusReply reply = *allDevicesCallWatcher; if( reply.isValid() ) { //qDebug() << "Connected to Cagibi, listing of UPnP devices/services started."; const DeviceTypeMap deviceTypeMap = reply; onDevicesAdded( deviceTypeMap ); } else { //qDebug() << "Could not connect to Cagibi, no listing of UPnP devices/services."; //qDebug() << "Error: " << reply.error().name(); } delete allDevicesCallWatcher; } void UpnpNetworkBuilder::addUPnPDevices( const QList& upnpDevices ) { QList addedDevices; QList addedServices; QList& deviceList = mNetworkPrivate->deviceList(); foreach( const Cagibi::Device& upnpDevice, upnpDevices ) { if( upnpDevice.hasParentDevice() ) continue; const QString ipAddress = upnpDevice.ipAddress(); - NetDevicePrivate* d = 0; - const NetDevice* deviceOfService = 0; + NetDevicePrivate* d = nullptr; + const NetDevice* deviceOfService = nullptr; foreach( const NetDevice& device, deviceList ) { const bool isSameAddress = ( device.ipAddress() == ipAddress ); //qDebug()<<"existing device:"<setIpAddress( ipAddress ); NetDevice device( d ); addedDevices.append( device ); deviceList.append( device ); deviceOfService = &deviceList.last(); //qDebug()<<"new device:"<canCreateNetSystemFromUpnp(upnpDevice) ) { // TODO: here we should rather see if this service already exists netServicePrivate = factory->createNetService( upnpDevice, *deviceOfService ); break; } } NetService netService( netServicePrivate ); d->addService( netService ); addedServices.append( netService ); //qDebug()<<"new service:"< d->type() ) { d->setType( deviceTypeByService ); if( ! deviceName.isEmpty() ) d->setName( deviceName ); } } } if( ! addedDevices.isEmpty() ) mNetworkPrivate->emitDevicesAdded( addedDevices ); if( ! addedServices.isEmpty() ) mNetworkPrivate->emitServicesAdded( addedServices ); } void UpnpNetworkBuilder::removeUPnPDevices( const QList& upnpDevices ) { QList removedDevices; QList removedServices; QList& deviceList = mNetworkPrivate->deviceList(); foreach( const Cagibi::Device& upnpDevice, upnpDevices ) { const QString ipAddress = upnpDevice.ipAddress(); QMutableListIterator it( deviceList ); while( it.hasNext()) { const NetDevice& device = it.next(); if( device.ipAddress() == ipAddress ) { QString id; foreach( const UpnpNetSystemAble* factory, mNetSystemFactoryList ) { if( factory->canCreateNetSystemFromUpnp(upnpDevice) ) { id = factory->upnpId( upnpDevice ); break; } } NetDevicePrivate* d = device.dPtr(); NetService netService = d->removeService( id ); if( ! netService.isValid() ) break; removedServices.append( netService ); // remove device on last service if( d->serviceList().count() == 0 ) { removedDevices.append( device ); // remove only after taking copy from reference into removed list it.remove(); } break; } } } if( ! removedServices.isEmpty() ) mNetworkPrivate->emitServicesRemoved( removedServices ); if( ! removedDevices.isEmpty() ) mNetworkPrivate->emitDevicesRemoved( removedDevices ); } void UpnpNetworkBuilder::onDevicesAdded( const DeviceTypeMap& deviceTypeMap ) { DeviceTypeMap::ConstIterator it = deviceTypeMap.constBegin(); DeviceTypeMap::ConstIterator end = deviceTypeMap.constEnd(); for( ; it != end; ++it ) { const QString udn = it.key(); QList args; args << udn; mCagibiDeviceListDBusProxy->callWithCallback( QLatin1String("deviceDetails"), args, - this, SLOT(onAddedDeviceDetails(Cagibi::Device)), 0 ); + this, SLOT(onAddedDeviceDetails(Cagibi::Device)), nullptr ); } } void UpnpNetworkBuilder::onDevicesRemoved( const DeviceTypeMap& deviceTypeMap ) { QList upnpDevices; DeviceTypeMap::ConstIterator it = deviceTypeMap.constBegin(); DeviceTypeMap::ConstIterator end = deviceTypeMap.constEnd(); for( ; it != end; ++it ) { QHash::Iterator adIt = mActiveDevices.find( it.key() ); if( adIt != mActiveDevices.end() ) { //qDebug()<<"removing UPnP device" << adIt.value().friendlyName(); upnpDevices.append( adIt.value() ); mActiveDevices.erase( adIt ); } } removeUPnPDevices( upnpDevices ); } void UpnpNetworkBuilder::onAddedDeviceDetails( const Cagibi::Device& device ) { // currently only root devices are shown if( device.hasParentDevice() ) return; mActiveDevices.insert( device.udn(), device ); QList devices; devices.append( device ); addUPnPDevices( devices ); } void UpnpNetworkBuilder::onCagibiServiceOwnerChanged( const QString& serviceName, const QString& oldOwner, const QString& newOwner ) { Q_UNUSED(serviceName); Q_UNUSED(newOwner); // old service disappeared? if( ! oldOwner.isEmpty() ) { //qDebug()<<"Cagibi disappeared, removing all UPnP devices"; // remove all registered UPnP devices QList upnpDevices = mActiveDevices.values(); mActiveDevices.clear(); removeUPnPDevices( upnpDevices ); } if( ! newOwner.isEmpty() ) queryCurrentDevices(); } UpnpNetworkBuilder::~UpnpNetworkBuilder() { } } diff --git a/nfs/kio_nfs.cpp b/nfs/kio_nfs.cpp index 7f64dca9..f363930f 100644 --- a/nfs/kio_nfs.cpp +++ b/nfs/kio_nfs.cpp @@ -1,735 +1,735 @@ /* This file is part of the KDE project Copyright(C) 2000 Alexander Neundorf , 2014 Mathias Tillman 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_nfs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "nfsv2.h" #include "nfsv3.h" using namespace KIO; using namespace std; Q_LOGGING_CATEGORY(LOG_KIO_NFS, "kde.kio-nfs") extern "C" int Q_DECL_EXPORT kdemain(int argc, char** argv); int kdemain(int argc, char** argv) { if (argc != 4) { fprintf(stderr, "Usage: kio_nfs protocol domain-socket1 domain-socket2\n"); exit(-1); } qCDebug(LOG_KIO_NFS) << "NFS: kdemain: starting"; NFSSlave slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } NFSSlave::NFSSlave(const QByteArray& pool, const QByteArray& app) : KIO::SlaveBase("nfs", pool, app), - m_protocol(NULL) + m_protocol(nullptr) { qCDebug(LOG_KIO_NFS) << pool << app; } NFSSlave::~NFSSlave() { - if (m_protocol != NULL) { + if (m_protocol != nullptr) { delete m_protocol; } } void NFSSlave::openConnection() { qCDebug(LOG_KIO_NFS) << "openConnection"; - if (m_protocol != NULL) { + if (m_protocol != nullptr) { m_protocol->openConnection(); } else { bool connectionError = false; int version = 4; while (version > 1) { qCDebug(LOG_KIO_NFS) << "Trying NFS version" << version; // We need to create a new NFS protocol handler switch (version) { case 4: { // TODO qCDebug(LOG_KIO_NFS) << "NFSv4 is not supported at this time"; } break; case 3: { m_protocol = new NFSProtocolV3(this); } break; case 2: { m_protocol = new NFSProtocolV2(this); } break; } // Unimplemented protocol version - if (m_protocol == NULL) { + if (m_protocol == nullptr) { version--; continue; } m_protocol->setHost(m_host); if (m_protocol->isCompatible(connectionError)) { break; } version--; delete m_protocol; - m_protocol = NULL; + m_protocol = nullptr; } - if (m_protocol == NULL) { + if (m_protocol == nullptr) { // If we could not find a compatible protocol, send an error. if (!connectionError) { error(KIO::ERR_COULD_NOT_CONNECT, i18n("%1: Unsupported NFS version", m_host)); } else { error(KIO::ERR_COULD_NOT_CONNECT, m_host); } } else { // Otherwise we open the connection m_protocol->openConnection(); } } } void NFSSlave::closeConnection() { qCDebug(LOG_KIO_NFS); - if (m_protocol != NULL) { + if (m_protocol != nullptr) { m_protocol->closeConnection(); } } void NFSSlave::setHost(const QString& host, quint16 /*port*/, const QString& /*user*/, const QString& /*pass*/) { qCDebug(LOG_KIO_NFS); - if (m_protocol != NULL) { + if (m_protocol != nullptr) { // New host? New protocol! if (m_host != host) { qCDebug(LOG_KIO_NFS) << "Deleting old protocol"; delete m_protocol; - m_protocol = NULL; + m_protocol = nullptr; } else { m_protocol->setHost(host); } } m_host = host; } void NFSSlave::put(const QUrl& url, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->put(url, _mode, _flags); } } void NFSSlave::get(const QUrl& url) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->get(url); } } void NFSSlave::listDir(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; if (verifyProtocol()) { m_protocol->listDir(url); } } void NFSSlave::symlink(const QString& target, const QUrl& dest, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->symlink(target, dest, _flags); } } void NFSSlave::stat(const QUrl& url) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->stat(url); } } void NFSSlave::mkdir(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->mkdir(url, permissions); } } void NFSSlave::del(const QUrl& url, bool isfile) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->del(url, isfile); } } void NFSSlave::chmod(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->chmod(url, permissions); } } void NFSSlave::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->rename(src, dest, flags); } } void NFSSlave::copy(const QUrl& src, const QUrl& dest, int mode, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS); if (verifyProtocol()) { m_protocol->copy(src, dest, mode, flags); } } bool NFSSlave::verifyProtocol() { - const bool haveProtocol = (m_protocol != NULL); + const bool haveProtocol = (m_protocol != nullptr); if (!haveProtocol) { openConnection(); - if (m_protocol == NULL) { + if (m_protocol == nullptr) { // We have failed.... :( qCDebug(LOG_KIO_NFS) << "Could not find a compatible protocol version!!"; return false; } // If we are not connected @openConnection will have sent an // error message to the client, so it's safe to return here. if (!m_protocol->isConnected()) { return false; } } else if (!m_protocol->isConnected()) { m_protocol->openConnection(); if (!m_protocol->isConnected()) { return false; } } if (m_protocol->isConnected()) { return true; } finished(); return false; } NFSFileHandle::NFSFileHandle() - : m_handle(NULL), + : m_handle(nullptr), m_size(0), - m_linkHandle(NULL), + m_linkHandle(nullptr), m_linkSize(0), m_isInvalid(true), m_isLink(false) { } NFSFileHandle::NFSFileHandle(const NFSFileHandle& src) : NFSFileHandle() { (*this) = src; } NFSFileHandle::NFSFileHandle(const fhandle3& src) : NFSFileHandle() { (*this) = src; } NFSFileHandle::NFSFileHandle(const fhandle& src) : NFSFileHandle() { (*this) = src; } NFSFileHandle::NFSFileHandle(const nfs_fh3& src) : NFSFileHandle() { (*this) = src; } NFSFileHandle::NFSFileHandle(const nfs_fh& src) : NFSFileHandle() { (*this) = src; } NFSFileHandle::~NFSFileHandle() { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; } - if (m_linkHandle != NULL) { + if (m_linkHandle != nullptr) { delete [] m_linkHandle; } } void NFSFileHandle::toFH(nfs_fh3& fh) const { fh.data.data_len = m_size; fh.data.data_val = m_handle; } void NFSFileHandle::toFH(nfs_fh& fh) const { memcpy(fh.data, m_handle, m_size); } void NFSFileHandle::toFHLink(nfs_fh3& fh) const { fh.data.data_len = m_linkSize; fh.data.data_val = m_linkHandle; } void NFSFileHandle::toFHLink(nfs_fh& fh) const { memcpy(fh.data, m_linkHandle, m_size); } NFSFileHandle& NFSFileHandle::operator=(const NFSFileHandle& src) { if (src.m_size > 0) { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; - m_handle = NULL; + m_handle = nullptr; } m_size = src.m_size; m_handle = new char[m_size]; memcpy(m_handle, src.m_handle, m_size); } if (src.m_linkSize > 0) { - if (m_linkHandle != NULL) { + if (m_linkHandle != nullptr) { delete [] m_linkHandle; - m_linkHandle = NULL; + m_linkHandle = nullptr; } m_linkSize = src.m_linkSize; m_linkHandle = new char[m_linkSize]; memcpy(m_linkHandle, src.m_linkHandle, m_linkSize); } m_isInvalid = src.m_isInvalid; m_isLink = src.m_isLink; return *this; } NFSFileHandle& NFSFileHandle::operator=(const fhandle3& src) { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; - m_handle = NULL; + m_handle = nullptr; } m_size = src.fhandle3_len; m_handle = new char[m_size]; memcpy(m_handle, src.fhandle3_val, m_size); m_isInvalid = false; return *this; } NFSFileHandle& NFSFileHandle::operator=(const fhandle& src) { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; - m_handle = NULL; + m_handle = nullptr; } m_size = NFS_FHSIZE; m_handle = new char[m_size]; memcpy(m_handle, src, m_size); m_isInvalid = false; return *this; } NFSFileHandle& NFSFileHandle::operator=(const nfs_fh3& src) { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; - m_handle = NULL; + m_handle = nullptr; } m_size = src.data.data_len; m_handle = new char[m_size]; memcpy(m_handle, src.data.data_val, m_size); m_isInvalid = false; return *this; } NFSFileHandle& NFSFileHandle::operator=(const nfs_fh& src) { - if (m_handle != NULL) { + if (m_handle != nullptr) { delete [] m_handle; - m_handle = NULL; + m_handle = nullptr; } m_size = NFS_FHSIZE; m_handle = new char[m_size]; memcpy(m_handle, src.data, m_size); m_isInvalid = false; return *this; } void NFSFileHandle::setLinkSource(const nfs_fh3& src) { - if (m_linkHandle != NULL) { + if (m_linkHandle != nullptr) { delete [] m_linkHandle; - m_linkHandle = NULL; + m_linkHandle = nullptr; } m_linkSize = src.data.data_len; m_linkHandle = new char[m_linkSize]; memcpy(m_linkHandle, src.data.data_val, m_linkSize); m_isLink = true; } void NFSFileHandle::setLinkSource(const nfs_fh& src) { - if (m_linkHandle != NULL) { + if (m_linkHandle != nullptr) { delete [] m_linkHandle; - m_linkHandle = NULL; + m_linkHandle = nullptr; } m_linkSize = NFS_FHSIZE; m_linkHandle = new char[m_linkSize]; memcpy(m_linkHandle, src.data, m_linkSize); m_isLink = true; } NFSProtocol::NFSProtocol(NFSSlave* slave) : m_slave(slave) { } void NFSProtocol::copy(const QUrl& src, const QUrl& dest, int mode, KIO::JobFlags flags) { if (src.isLocalFile()) { copyTo(src, dest, mode, flags); } else if (dest.isLocalFile()) { copyFrom(src, dest, mode, flags); } else { copySame(src, dest, mode, flags); } } void NFSProtocol::addExportedDir(const QString& path) { m_exportedDirs.append(path); } const QStringList& NFSProtocol::getExportedDirs() { return m_exportedDirs; } bool NFSProtocol::isExportedDir(const QString& path) { // If the path is the root filesystem we always return true. if (QFileInfo(path).isRoot()) { return true; } for (QStringList::const_iterator it = m_exportedDirs.constBegin(); it != m_exportedDirs.constEnd(); ++it) { if (path.length() < (*it).length() && (*it).startsWith(path)) { QString rest = (*it).mid(path.length()); if (rest.isEmpty() || rest[0] == QDir::separator()) { qCDebug(LOG_KIO_NFS) << "isExportedDir" << path << "returning true"; return true; } } } return false; } void NFSProtocol::removeExportedDir(const QString& path) { m_exportedDirs.removeOne(path); } void NFSProtocol::addFileHandle(const QString& path, NFSFileHandle fh) { m_handleCache.insert(path, fh); } NFSFileHandle NFSProtocol::getFileHandle(const QString& path) { if (!isConnected()) { return NFSFileHandle(); } if (!isValidPath(path)) { qCDebug(LOG_KIO_NFS) << path << "is not a valid path"; return NFSFileHandle(); } // The handle may already be in the cache, check it now. // The exported dirs are always in the cache. if (m_handleCache.contains(path)) { return m_handleCache[path]; } // Loop detected, abort. if (QFileInfo(path).path() == path) { return NFSFileHandle(); } // Look up the file handle from the procotol NFSFileHandle childFH = lookupFileHandle(path); if (!childFH.isInvalid()) { m_handleCache.insert(path, childFH); } return childFH; } void NFSProtocol::removeFileHandle(const QString& path) { m_handleCache.remove(path); } bool NFSProtocol::isValidPath(const QString& path) { if (path.isEmpty() || path == QDir::separator()) { return true; } for (QStringList::const_iterator it = m_exportedDirs.constBegin(); it != m_exportedDirs.constEnd(); ++it) { if ((path.length() == (*it).length() && path.startsWith((*it))) || (path.startsWith((*it) + QDir::separator()))) { return true; } } return false; } bool NFSProtocol::isValidLink(const QString& parentDir, const QString& linkDest) { if (linkDest.isEmpty()) { return false; } if (QFileInfo(linkDest).isAbsolute()) { return (!getFileHandle(linkDest).isInvalid()); } else { QString absDest = QFileInfo(parentDir, linkDest).filePath(); absDest = QDir::cleanPath(absDest); return (!getFileHandle(absDest).isInvalid()); } return false; } int NFSProtocol::openConnection(const QString& host, int prog, int vers, CLIENT*& client, int& sock) { if (host.isEmpty()) { return KIO::ERR_UNKNOWN_HOST; } struct sockaddr_in server_addr; if (host[0] >= '0' && host[0] <= '9') { server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(host.toLatin1()); } else { struct hostent* hp = gethostbyname(host.toLatin1()); - if (hp == 0) { + if (hp == nullptr) { return KIO::ERR_UNKNOWN_HOST; } server_addr.sin_family = AF_INET; memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length); } server_addr.sin_port = 0; sock = RPC_ANYSOCK; client = clnttcp_create(&server_addr, prog, vers, &sock, 0, 0); - if (client == 0) { + if (client == nullptr) { server_addr.sin_port = 0; sock = RPC_ANYSOCK; timeval pertry_timeout; pertry_timeout.tv_sec = 3; pertry_timeout.tv_usec = 0; client = clntudp_create(&server_addr, prog, vers, pertry_timeout, &sock); - if (client == 0) { + if (client == nullptr) { ::close(sock); return KIO::ERR_COULD_NOT_CONNECT; } } QString hostName = QHostInfo::localHostName(); QString domainName = QHostInfo::localDomainName(); if (!domainName.isEmpty()) { hostName = hostName + QLatin1Char('.') + domainName; } - client->cl_auth = authunix_create(hostName.toUtf8().data(), geteuid(), getegid(), 0, 0); + client->cl_auth = authunix_create(hostName.toUtf8().data(), geteuid(), getegid(), 0, nullptr); return 0; } bool NFSProtocol::checkForError(int clientStat, int nfsStat, const QString& text) { if (clientStat != RPC_SUCCESS) { qCDebug(LOG_KIO_NFS) << "RPC error" << clientStat << text; m_slave->error(KIO::ERR_INTERNAL_SERVER, i18n("RPC error %1", clientStat)); return false; } if (nfsStat != NFS_OK) { qCDebug(LOG_KIO_NFS) << "NFS error:" << nfsStat << text; switch (nfsStat) { case NFSERR_PERM: m_slave->error(KIO::ERR_ACCESS_DENIED, text); break; case NFSERR_NOENT: m_slave->error(KIO::ERR_DOES_NOT_EXIST, text); break; //does this mapping make sense ? case NFSERR_IO: m_slave->error(KIO::ERR_INTERNAL_SERVER, text); break; //does this mapping make sense ? case NFSERR_NXIO: m_slave->error(KIO::ERR_DOES_NOT_EXIST, text); break; case NFSERR_ACCES: m_slave->error(KIO::ERR_ACCESS_DENIED, text); break; case NFSERR_EXIST: m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, text); break; //does this mapping make sense ? case NFSERR_NODEV: m_slave->error(KIO::ERR_DOES_NOT_EXIST, text); break; case NFSERR_NOTDIR: m_slave->error(KIO::ERR_IS_FILE, text); break; case NFSERR_ISDIR: m_slave->error(KIO::ERR_IS_DIRECTORY, text); break; //does this mapping make sense ? case NFSERR_FBIG: m_slave->error(KIO::ERR_INTERNAL_SERVER, text); break; //does this mapping make sense ? case NFSERR_NOSPC: m_slave->error(KIO::ERR_INTERNAL_SERVER, i18n("No space left on device")); break; case NFSERR_ROFS: m_slave->error(KIO::ERR_COULD_NOT_WRITE, i18n("Read only file system")); break; case NFSERR_NAMETOOLONG: m_slave->error(KIO::ERR_INTERNAL_SERVER, i18n("Filename too long")); break; case NFSERR_NOTEMPTY: m_slave->error(KIO::ERR_COULD_NOT_RMDIR, text); break; //does this mapping make sense ? case NFSERR_DQUOT: m_slave->error(KIO::ERR_INTERNAL_SERVER, i18n("Disk quota exceeded")); break; case NFSERR_STALE: m_slave->error(KIO::ERR_DOES_NOT_EXIST, text); break; default: m_slave->error(KIO::ERR_UNKNOWN, i18n("NFS error %1 - %2", nfsStat, text)); break; } return false; } return true; } void NFSProtocol::createVirtualDirEntry(UDSEntry& entry) { entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.insert(KIO::UDSEntry::UDS_USER, QString::fromLatin1("root")); entry.insert(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1("root")); // Dummy size. entry.insert(KIO::UDSEntry::UDS_SIZE, 0); } #include "moc_kio_nfs.cpp" diff --git a/nfs/nfsv2.cpp b/nfs/nfsv2.cpp index 67a5ed1a..ae231a16 100644 --- a/nfs/nfsv2.cpp +++ b/nfs/nfsv2.cpp @@ -1,1858 +1,1858 @@ /* This file is part of the KDE project Copyright(C) 2000 Alexander Neundorf , 2014 Mathias Tillman 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 #include // This is needed on Solaris so that rpc.h defines clnttcp_create etc. #ifndef PORTMAP #define PORTMAP #endif #include // for rpc calls #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfsv2.h" // This is for NFS version 2. #define NFSPROG 100003UL #define NFSVERS 2UL NFSProtocolV2::NFSProtocolV2(NFSSlave* slave) : NFSProtocol(slave), m_slave(slave), - m_mountClient(0), + m_mountClient(nullptr), m_mountSock(-1), - m_nfsClient(0), + m_nfsClient(nullptr), m_nfsSock(-1) { qCDebug(LOG_KIO_NFS) << "NFS2::NFS2"; clnt_timeout.tv_sec = 20; clnt_timeout.tv_usec = 0; } NFSProtocolV2::~NFSProtocolV2() { closeConnection(); } bool NFSProtocolV2::isCompatible(bool& connectionError) { int ret = -1; - CLIENT* client = NULL; + CLIENT* client = nullptr; int sock = 0; if (NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, client, sock) == 0) { // Check if the NFS version is compatible ret = clnt_call(client, NFSPROC_NULL, - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_void, NULL, clnt_timeout); + (xdrproc_t) xdr_void, nullptr, + (xdrproc_t) xdr_void, nullptr, clnt_timeout); connectionError = false; } else { qCDebug(LOG_KIO_NFS) << "openConnection failed"; connectionError = true; } if (sock != -1) { ::close(sock); } - if (client != NULL) { + if (client != nullptr) { CLNT_DESTROY(client); } qCDebug(LOG_KIO_NFS) << ret; return (ret == RPC_SUCCESS); } bool NFSProtocolV2::isConnected() const { - return (m_nfsClient != 0); + return (m_nfsClient != nullptr); } void NFSProtocolV2::closeConnection() { qCDebug(LOG_KIO_NFS); // Unmount all exported dirs(if any) - if (m_mountClient != 0) { + if (m_mountClient != nullptr) { clnt_call(m_mountClient, MOUNTPROC_UMNTALL, - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_void, nullptr, + (xdrproc_t) xdr_void, nullptr, clnt_timeout); } if (m_mountSock >= 0) { ::close(m_mountSock); m_mountSock = -1; } if (m_nfsSock >= 0) { ::close(m_nfsSock); m_nfsSock = -1; } - if (m_mountClient != 0) { + if (m_mountClient != nullptr) { CLNT_DESTROY(m_mountClient); - m_mountClient = 0; + m_mountClient = nullptr; } - if (m_nfsClient != 0) { + if (m_nfsClient != nullptr) { CLNT_DESTROY(m_nfsClient); - m_nfsClient = 0; + m_nfsClient = nullptr; } } NFSFileHandle NFSProtocolV2::lookupFileHandle(const QString& path) { int rpcStatus; diropres res; if (lookupHandle(path, rpcStatus, res)) { NFSFileHandle fh = res.diropres_u.diropres.file; // It it a link? Get the link target. if (res.diropres_u.diropres.attributes.type == NFLNK) { nfs_fh readLinkArgs; fh.toFH(readLinkArgs); char dataBuffer[NFS_MAXPATHLEN]; readlinkres readLinkRes; memset(&readLinkRes, 0, sizeof(readLinkRes)); readLinkRes.readlinkres_u.data = dataBuffer; int rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_readlinkres, reinterpret_cast(&readLinkRes), clnt_timeout); if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS_OK) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(QFileInfo(path).path(), linkDest).absoluteFilePath(); } diropres linkRes; if (lookupHandle(linkPath, rpcStatus, linkRes)) { NFSFileHandle linkFH = linkRes.diropres_u.diropres.file; linkFH.setLinkSource(res.diropres_u.diropres.file); qCDebug(LOG_KIO_NFS) << "Found target -" << linkPath; return linkFH; } } // If we have reached this point the file is a link, but we failed to get the target. fh.setBadLink(); } return fh; } return NFSFileHandle(); } /* Open connection connects to the mount daemon on the server side. In order to do this it needs authentication and calls auth_unix_create(). Then it asks the mount daemon for the exported shares. Then it tries to mount all these shares. If this succeeded for at least one of them, a client for the nfs daemon is created. */ void NFSProtocolV2::openConnection() { qCDebug(LOG_KIO_NFS) << m_currentHost; int connErr; if ((connErr = NFSProtocol::openConnection(m_currentHost, MOUNTPROG, MOUNTVERS, m_mountClient, m_mountSock)) != 0) { // Close the connection and send the error id to the slave closeConnection(); m_slave->error(connErr, m_currentHost); return; } exports exportlist; memset(&exportlist, 0, sizeof(exportlist)); int clnt_stat = clnt_call(m_mountClient, MOUNTPROC_EXPORT, - (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_exports, reinterpret_cast(&exportlist), clnt_timeout); if (!checkForError(clnt_stat, 0, m_currentHost.toLatin1())) { return; } int exportsCount = 0; QStringList failList; fhstatus fhStatus; - for (; exportlist != 0; exportlist = exportlist->ex_next, exportsCount++) { + for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) { memset(&fhStatus, 0, sizeof(fhStatus)); clnt_stat = clnt_call(m_mountClient, MOUNTPROC_MNT, (xdrproc_t) xdr_dirpath, reinterpret_cast(&exportlist->ex_dir), (xdrproc_t) xdr_fhstatus, reinterpret_cast(&fhStatus), clnt_timeout); if (fhStatus.fhs_status == 0) { QString fname = QFileInfo(QDir("/"), exportlist->ex_dir).filePath(); // Check if the dir is already exported if (NFSProtocol::isExportedDir(fname)) { continue; } addFileHandle(fname, static_cast(fhStatus.fhstatus_u.fhs_fhandle)); addExportedDir(fname); } else { failList.append(exportlist->ex_dir); } } // Check if some exported dirs failed to mount if (failList.size() > 0) { m_slave->error(KIO::ERR_COULD_NOT_MOUNT, i18n("Failed to mount %1", failList.join(", "))); // All exports failed to mount, fail if (failList.size() == exportsCount) { closeConnection(); return; } } if ((connErr = NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); } m_slave->connected(); qCDebug(LOG_KIO_NFS) << "openConnection succeeded"; } void NFSProtocolV2::listDir(const QUrl& url) { // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString path(url.path()); // The root "directory" is just a list of the exported directories, // so list them here. if (isExportedDir(path)) { qCDebug(LOG_KIO_NFS) << "Listing exported dir"; QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith('/')) { name = name.mid(1); } if (name.indexOf('/') != -1) { name.truncate(name.indexOf('/')); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << (*it) << "found in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } readdirargs listargs; memset(&listargs, 0, sizeof(listargs)); listargs.count = 1024 * sizeof(entry); fh.toFH(listargs.dir); readdirres listres; QStringList filesToList; - entry* lastEntry = 0; + entry* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received. - if (lastEntry != 0) { + if (lastEntry != nullptr) { memcpy(listargs.cookie, lastEntry->cookie, NFS_COOKIESIZE); } int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READDIR, (xdrproc_t) xdr_readdirargs, reinterpret_cast(&listargs), (xdrproc_t) xdr_readdirres, reinterpret_cast(&listres), clnt_timeout); if (!checkForError(clnt_stat, listres.status, path)) { return; } - for (entry* dirEntry = listres.readdirres_u.reply.entries; dirEntry != 0; dirEntry = dirEntry->nextentry) { + for (entry* dirEntry = listres.readdirres_u.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name != QString(".") && dirEntry->name != QString("..")) { filesToList.append(QFile::decodeName(dirEntry->name)); } lastEntry = dirEntry; } } while (!listres.readdirres_u.reply.eof); KIO::UDSEntry entry; for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) { QString filePath = QFileInfo(QDir(path), (*it)).filePath(); int rpcStatus; diropres dirres; if (!lookupHandle(filePath, rpcStatus, dirres)) { qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status; // Try the next file instead of failing continue; } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); //is it a symlink ? if (dirres.diropres_u.diropres.attributes.type == NFLNK) { int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; diropres lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { attrstat attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.diropres_u.diropres.file; linkFH.setLinkSource(dirres.diropres_u.diropres.file); completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } } } if (badLink) { linkFH = dirres.diropres_u.diropres.file; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirres.diropres_u.diropres.attributes); } } else { addFileHandle(filePath, dirres.diropres_u.diropres.file); completeUDSEntry(entry, dirres.diropres_u.diropres.attributes); } m_slave->listEntry(entry); } m_slave->finished(); } void NFSProtocolV2::stat(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, path); createVirtualDirEntry(entry); m_slave->statEntry(entry); m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { qCDebug(LOG_KIO_NFS) << "File handle is invalid"; m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } int rpcStatus; attrstat attrAndStat; if (!getAttr(path, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, path); return; } const QFileInfo fileInfo(path); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName()); // Is it a symlink? if (attrAndStat.attrstat_u.attributes.type == NFLNK) { qCDebug(LOG_KIO_NFS) << "It's a symlink"; QString linkDest; int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (readLink(path, rpcStatus, readLinkRes, nameBuf)) { linkDest = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes); m_slave->statEntry(entry); m_slave->finished(); return; } qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest; entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); if (!isValidLink(fileInfo.path(), linkDest)) { completeBadLinkUDSEntry(entry, attrAndStat.attrstat_u.attributes); } else { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath(); } int rpcStatus; attrstat attrAndStat; if (!getAttr(linkPath, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, linkPath); return; } completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } } else { completeUDSEntry(entry, attrAndStat.attrstat_u.attributes); } m_slave->statEntry(entry); m_slave->finished(); } void NFSProtocolV2::setHost(const QString& host) { qCDebug(LOG_KIO_NFS) << host; if (host.isEmpty()) { m_slave->error(KIO::ERR_UNKNOWN_HOST, QString()); return; } if (host == m_currentHost) { return; } // Set the new host and close the current connection m_currentHost = host; closeConnection(); } void NFSProtocolV2::mkdir(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, path); return; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } createargs createArgs; fh.toFH(createArgs.where.dir); QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createArgs.where.name = tmpName.data(); if (permissions == -1) { createArgs.attributes.mode = 0755; } else { createArgs.attributes.mode = permissions; } diropres dirres; memset(&dirres, 0, sizeof(diropres)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC_MKDIR, (xdrproc_t) xdr_createargs, reinterpret_cast(&createArgs), (xdrproc_t) xdr_diropres, reinterpret_cast(&dirres), clnt_timeout); if (!checkForError(clnt_stat, dirres.status, path)) { return; } m_slave->finished(); } void NFSProtocolV2::del(const QUrl& url, bool) { int rpcStatus; nfsstat nfsStatus; if (!remove(url.path(), rpcStatus, nfsStatus)) { checkForError(rpcStatus, nfsStatus, url.path()); qCDebug(LOG_KIO_NFS) << "Could not delete" << url; return; } m_slave->finished(); } void NFSProtocolV2::chmod(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mode = permissions; int rpcStatus; nfsstat result; if (!setAttr(path, attributes, rpcStatus, result)) { checkForError(rpcStatus, result, path); return; } m_slave->finished(); } void NFSProtocolV2::get(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } readargs readArgs; fh.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; readres readRes; memset(&readRes, 0, sizeof(readres)); char buf[NFS_MAXDATA]; readRes.readres_u.reply.data.data_val = buf; bool validRead = false; int offset = 0; QByteArray readBuffer; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, path)) { return; } if (readArgs.offset == 0) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); const QMimeDatabase db; const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer); m_slave->mimeType(type.name()); } offset = readRes.readres_u.reply.data.data_len; readArgs.offset += offset; if (offset > 0) { validRead = true; readBuffer = QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, offset); m_slave->data(readBuffer); readBuffer.clear(); m_slave->processedSize(readArgs.offset); } } while (offset > 0); if (validRead) { m_slave->data(QByteArray()); m_slave->processedSize(readArgs.offset); } m_slave->finished(); } void NFSProtocolV2::put(const QUrl& url, int _mode, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS) << url << _mode; const QString destPath(url.path()); const QFileInfo fileInfo(destPath); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); return; } NFSFileHandle destFH = getFileHandle(destPath); if (destFH.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, destPath); return; } //the file exists and we don't want to overwrite if (!destFH.isInvalid() && (!(flags & KIO::Overwrite))) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; diropres dirOpRes; if (!create(destPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, fileInfo.fileName()); return; } destFH = dirOpRes.diropres_u.diropres.file.data; writeargs writeArgs; memset(&writeArgs, 0, sizeof(writeargs)); destFH.toFH(writeArgs.file); writeArgs.beginoffset = 0; writeArgs.totalcount = 0; writeArgs.offset = 0; attrstat attrStat; int result = 0, bytesWritten = 0; do { // Request new data m_slave->dataReq(); QByteArray buffer; result = m_slave->readData(buffer); char* data = buffer.data(); int bytesToWrite = buffer.size(), writeNow = 0; if (result > 0) { do { if (bytesToWrite > NFS_MAXDATA) { writeNow = NFS_MAXDATA; } else { writeNow = bytesToWrite; } writeArgs.data.data_val = data; writeArgs.data.data_len = writeNow; int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, fileInfo.fileName())) { return; } bytesWritten += writeNow; writeArgs.offset = bytesWritten; data = data + writeNow; bytesToWrite -= writeNow; } while (bytesToWrite > 0); } } while (result > 0); m_slave->finished(); } void NFSProtocolV2::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); if (isExportedDir(srcPath)) { m_slave->error(KIO::ERR_CANNOT_RENAME, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; nfsstat nfsStatus; if (!rename(src.path(), destPath, rpcStatus, nfsStatus)) { if (!checkForError(rpcStatus, nfsStatus, destPath)) { return; } } m_slave->finished(); } void NFSProtocolV2::copySame(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath = dest.path(); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { //get the link dest int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString linkPath = QString::fromLocal8Bit(readLinkRes.readlinkres_u.data); nfsstat linkRes; if (!symLink(linkPath, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes, linkPath); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bPartExists) { int rpcStatus; diropres partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) { if (partRes.diropres_u.diropres.attributes.type == NFDIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.diropres_u.diropres.attributes.size); if (bResume) { resumeOffset = partRes.diropres_u.diropres.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; diropres dirOpRes; if (!create(createPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, createPath); return; } destFH = dirOpRes.diropres_u.diropres.file.data; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } char buf[NFS_MAXDATA]; writeargs writeArgs; destFH.toFH(writeArgs.file); writeArgs.beginoffset = 0; writeArgs.totalcount = 0; writeArgs.offset = 0; writeArgs.data.data_val = buf; readargs readArgs; srcFH.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; if (bResume) { writeArgs.offset = resumeOffset; readArgs.offset = resumeOffset; } readres readRes; memset(&readRes, 0, sizeof(readres)); readRes.readres_u.reply.data.data_val = buf; attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.readres_u.reply.data.data_len; // We should only send out the total size and mimetype at the start of the transfer if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; writeArgs.data.data_len = bytesRead; clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore modification time int rpcStatus; attrstat attrRes; if (getAttr(srcPath, rpcStatus, attrRes)) { sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mtime.seconds = attrRes.attrstat_u.attributes.mtime.seconds; attributes.mtime.useconds = attrRes.attrstat_u.attributes.mtime.useconds; nfsstat attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes; } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV2::copyFrom(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); // The file exists and we don't want to overwrite if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { qCDebug(LOG_KIO_NFS) << "Is a link"; int rpcStatus; readlinkres readLinkRes; char nameBuf[NFS_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } QFile::link(QString::fromLocal8Bit(readLinkRes.readlinkres_u.data), destPath); m_slave->finished(); return; } bool bResume = false; const QFileInfo partInfo(destPath + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { m_slave->error(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = m_slave->canResume(partInfo.size()); } if (bPartExists && !bResume) { QFile::remove(partInfo.absoluteFilePath()); } QFile::OpenMode openMode; QString outFileName; if (bResume) { outFileName = partInfo.absoluteFilePath(); openMode = QFile::WriteOnly | QFile::Append; } else { outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath); openMode = QFile::WriteOnly | QFile::Truncate; } QFile destFile(outFileName); if (!bResume) { QFile::Permissions perms; if (_mode == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(_mode | QFile::WriteOwner); } destFile.setPermissions(perms); } if (!destFile.open(openMode)) { switch (destFile.error()) { case QFile::OpenError: if (bResume) { m_slave->error(KIO::ERR_CANNOT_RESUME, destPath); } else { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); } break; case QFile::PermissionsError: m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); break; default: m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); break; } return; } char buf[NFS_MAXDATA]; readargs readArgs; srcFH.toFH(readArgs.file); if (bResume) { readArgs.offset = partInfo.size(); } else { readArgs.offset = 0; } readArgs.count = NFS_MAXDATA; readArgs.totalcount = NFS_MAXDATA; readres readRes; memset(&readRes, 0, sizeof(readres)); readRes.readres_u.reply.data.data_val = buf; attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC_READ, (xdrproc_t) xdr_readargs, reinterpret_cast(&readArgs), (xdrproc_t) xdr_readres, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.readres_u.reply.data.data_len; if (readArgs.offset == 0) { m_slave->totalSize(readRes.readres_u.reply.attributes.size); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.readres_u.reply.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; if (destFile.write(readRes.readres_u.reply.data.data_val, bytesRead) != bytesRead) { m_slave->error(KIO::ERR_COULD_NOT_WRITE, destPath); error = true; break; } m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); // Close the file so we can modify the modification time later. destFile.close(); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep const int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(partInfo.absoluteFilePath()); } } } else { // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); if (QFile::exists(destPath)) { QFile::remove(destPath); } if (!QFile::rename(sPart, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << sPart << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { qCDebug(LOG_KIO_NFS) << "Setting modification time to" << dt.toTime_t(); struct utimbuf utbuf; utbuf.actime = QFileInfo(destPath).lastRead().toTime_t(); // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(destPath).constData(), &utbuf); } } qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV2::copyTo(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; // The source does not exist, how strange. const QString srcPath(src.path()); if (!QFile::exists(srcPath)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite. if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. const QString symlinkTarget = QFile::symLinkTarget(srcPath); if (!symlinkTarget.isEmpty()) { int rpcStatus; nfsstat linkRes; if (!symLink(symlinkTarget, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes, symlinkTarget); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bPartExists) { int rpcStatus; diropres partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.diropres_u.diropres.attributes.size > 0) { if (partRes.diropres_u.diropres.attributes.type == NFDIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.diropres_u.diropres.attributes.size); if (bResume) { resumeOffset = partRes.diropres_u.diropres.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Open the source file QFile srcFile(srcPath); if (!srcFile.open(QIODevice::ReadOnly)) { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath); return; } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; diropres dirOpRes; if (!create(createPath, _mode, rpcStatus, dirOpRes)) { checkForError(rpcStatus, dirOpRes.status, createPath); return; } destFH = dirOpRes.diropres_u.diropres.file.data; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Send the total size to the slave. m_slave->totalSize(srcFile.size()); // Set up write arguments. char buf[NFS_MAXDATA]; writeargs writeArgs; memset(&writeArgs, 0, sizeof(writeargs)); destFH.toFH(writeArgs.file); writeArgs.data.data_val = buf; writeArgs.beginoffset = 0; writeArgs.totalcount = 0; if (bResume) { writeArgs.offset = resumeOffset; } else { writeArgs.offset = 0; } attrstat attrStat; memset(&attrStat, 0, sizeof(attrstat)); bool error = false; int bytesRead = 0; do { bytesRead = srcFile.read(writeArgs.data.data_val, NFS_MAXDATA); if (bytesRead < 0) { m_slave->error(KIO::ERR_COULD_NOT_READ, srcPath); error = true; break; } if (bytesRead > 0) { writeArgs.data.data_len = bytesRead; int clnt_stat = clnt_call(m_nfsClient, NFSPROC_WRITE, (xdrproc_t) xdr_writeargs, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_attrstat, reinterpret_cast(&attrStat), clnt_timeout); if (!checkForError(clnt_stat, attrStat.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(writeArgs.offset); } } while (bytesRead > 0); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "Failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { sattr attributes; memset(&attributes, 0xFF, sizeof(attributes)); attributes.mtime.seconds = dt.toTime_t(); attributes.mtime.useconds = attributes.mtime.seconds * 1000000ULL; int rpcStatus; nfsstat attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes; } } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(writeArgs.offset); m_slave->finished(); } } void NFSProtocolV2::symlink(const QString& target, const QUrl& dest, KIO::JobFlags flags) { const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; nfsstat res; if (!symLink(target, destPath, rpcStatus, res)) { checkForError(rpcStatus, res, destPath); return; } m_slave->finished(); } bool NFSProtocolV2::create(const QString& path, int mode, int& rpcStatus, diropres& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result.status = NFSERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createargs args; directoryFH.toFH(args.where.dir); args.where.name = tmpName.data(); memset(&args.attributes, 0xFF, sizeof(sattr)); if (mode == -1) { args.attributes.mode = 0644; } else { args.attributes.mode = mode; } args.attributes.uid = geteuid(); args.attributes.gid = getegid(); args.attributes.size = 0; rpcStatus = clnt_call(m_nfsClient, NFSPROC_CREATE, (xdrproc_t) xdr_createargs, reinterpret_cast(&args), (xdrproc_t) xdr_diropres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::getAttr(const QString& path, int& rpcStatus, attrstat& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const NFSFileHandle fileFH = getFileHandle(path); if (fileFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } nfs_fh fh; fileFH.toFH(fh); rpcStatus = clnt_call(m_nfsClient, NFSPROC_GETATTR, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&fh), (xdrproc_t) xdr_attrstat, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::lookupHandle(const QString& path, int& rpcStatus, diropres& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFSERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle parentFH = getFileHandle(fileInfo.path()); if (parentFH.isInvalid()) { result.status = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); diropargs dirargs; memset(&dirargs, 0, sizeof(diropargs)); parentFH.toFH(dirargs.dir); dirargs.name = tmpName.data(); memset(&result, 0, sizeof(diropres)); rpcStatus = clnt_call(m_nfsClient, NFSPROC_LOOKUP, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_diropres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::readLink(const QString& path, int& rpcStatus, readlinkres& result, char* dataBuffer) { const NFSFileHandle fh = getFileHandle(path); nfs_fh nfsFH; if (fh.isLink() && !fh.isBadLink()) { fh.toFHLink(nfsFH); } else { fh.toFH(nfsFH); } result.readlinkres_u.data = dataBuffer; rpcStatus = clnt_call(m_nfsClient, NFSPROC_READLINK, (xdrproc_t) xdr_nfs_fh, reinterpret_cast(&nfsFH), (xdrproc_t) xdr_readlinkres, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS_OK); } bool NFSProtocolV2::remove(const QString& path) { int rpcStatus; nfsstat nfsStatus; return remove(path, rpcStatus, nfsStatus); } bool NFSProtocolV2::remove(const QString& path, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result = NFSERR_PERM; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } int rpcLookupStatus; diropres lookupRes; if (!lookupHandle(path, rpcLookupStatus, lookupRes)) { result = NFSERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); diropargs dirargs; memset(&dirargs, 0, sizeof(diropargs)); directoryFH.toFH(dirargs.dir); dirargs.name = tmpName.data(); if (lookupRes.diropres_u.diropres.attributes.type != NFDIR) { rpcStatus = clnt_call(m_nfsClient, NFSPROC_REMOVE, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); } else { rpcStatus = clnt_call(m_nfsClient, NFSPROC_RMDIR, (xdrproc_t) xdr_diropargs, reinterpret_cast(&dirargs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); } bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK); if (ret) { removeFileHandle(path); } return ret; } bool NFSProtocolV2::rename(const QString& src, const QString& dest) { int rpcStatus; nfsstat result; return rename(src, dest, rpcStatus, result); } bool NFSProtocolV2::rename(const QString& src, const QString& dest, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << src << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const QFileInfo srcFileInfo(src); if (isExportedDir(srcFileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path()); if (srcDirectoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } const QFileInfo destFileInfo(dest); if (isExportedDir(destFileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path()); if (destDirectoryFH.isInvalid()) { result = NFSERR_NOENT; return false; } renameargs renameArgs; memset(&renameArgs, 0, sizeof(renameargs)); QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName()); srcDirectoryFH.toFH(renameArgs.from.dir); renameArgs.from.name = srcByteName.data(); QByteArray destByteName = QFile::encodeName(destFileInfo.fileName()); destDirectoryFH.toFH(renameArgs.to.dir); renameArgs.to.name = destByteName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC_RENAME, (xdrproc_t) xdr_renameargs, reinterpret_cast(&renameArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); bool ret = (rpcStatus == RPC_SUCCESS && result == NFS_OK); if (ret) { // Can we actually find the new handle? int lookupStatus; diropres lookupRes; if (lookupHandle(dest, lookupStatus, lookupRes)) { // Remove the old file, and add the new one removeFileHandle(src); addFileHandle(dest, lookupRes.diropres_u.diropres.file); } } return ret; } bool NFSProtocolV2::setAttr(const QString& path, const sattr& attributes, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result = NFSERR_NOENT; return false; } sattrargs sAttrArgs; fh.toFH(sAttrArgs.file); memcpy(&sAttrArgs.attributes, &attributes, sizeof(attributes)); rpcStatus = clnt_call(m_nfsClient, NFSPROC_SETATTR, (xdrproc_t) xdr_sattrargs, reinterpret_cast(&sAttrArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result == NFS_OK); } bool NFSProtocolV2::symLink(const QString& target, const QString& dest, int& rpcStatus, nfsstat& result) { qCDebug(LOG_KIO_NFS) << target << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); // Remove dest first, we don't really care about the return value at this point, // the symlink call will fail if dest was not removed correctly. remove(dest); const QFileInfo fileInfo(dest); if (isExportedDir(fileInfo.path())) { result = NFSERR_ACCES; return false; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid()) { result = NFSERR_NOENT; return false; } QByteArray fromBytes = QFile::encodeName(fileInfo.fileName()); QByteArray toBytes = QFile::encodeName(target); symlinkargs symLinkArgs; memset(&symLinkArgs, 0, sizeof(symLinkArgs)); fh.toFH(symLinkArgs.from.dir); symLinkArgs.from.name = fromBytes.data(); symLinkArgs.to = toBytes.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC_SYMLINK, (xdrproc_t) xdr_symlinkargs, reinterpret_cast(&symLinkArgs), (xdrproc_t) xdr_nfsstat, reinterpret_cast(&result), clnt_timeout); // Add the new handle to the cache NFSFileHandle destFH = getFileHandle(dest); if (!destFH.isInvalid()) { addFileHandle(dest, destFH); } return (rpcStatus == RPC_SUCCESS && result == NFS_OK); } void NFSProtocolV2::completeUDSEntry(KIO::UDSEntry& entry, const fattr& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, attributes.size); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777)); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, attributes.mode & S_IFMT); // extract file type QString str; const uid_t uid = attributes.uid; if (!m_usercache.contains(uid)) { struct passwd* user = getpwuid(uid); if (user) { m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); str = user->pw_name; } else { str = QString::number(uid); } } else { str = m_usercache.value(uid); } entry.insert(KIO::UDSEntry::UDS_USER, str); const gid_t gid = attributes.gid; if (!m_groupcache.contains(gid)) { struct group* grp = getgrgid(gid); if (grp) { m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); str = grp->gr_name; } else { str = QString::number(gid); } } else { str = m_groupcache.value(gid); } entry.insert(KIO::UDSEntry::UDS_GROUP, str); } void NFSProtocolV2::completeBadLinkUDSEntry(KIO::UDSEntry& entry, const fattr& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, 0LL); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); entry.insert(KIO::UDSEntry::UDS_USER, attributes.uid); entry.insert(KIO::UDSEntry::UDS_GROUP, attributes.gid); } diff --git a/nfs/nfsv3.cpp b/nfs/nfsv3.cpp index 42b40a13..007b3abd 100644 --- a/nfs/nfsv3.cpp +++ b/nfs/nfsv3.cpp @@ -1,2195 +1,2195 @@ /* This file is part of the KDE project Copyright(C) 2000 Alexander Neundorf , 2014 Mathias Tillman 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 #include // This is needed on Solaris so that rpc.h defines clnttcp_create etc. #ifndef PORTMAP #define PORTMAP #endif #include // for rpc calls #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfsv3.h" // This ioslave is for NFS version 3. #define NFSPROG 100003UL #define NFSVERS 3UL #define NFS3_MAXDATA 32768 #define NFS3_MAXPATHLEN PATH_MAX NFSProtocolV3::NFSProtocolV3(NFSSlave* slave) : NFSProtocol(slave), m_slave(slave), - m_mountClient(0), + m_mountClient(nullptr), m_mountSock(-1), - m_nfsClient(0), + m_nfsClient(nullptr), m_nfsSock(-1), m_readBufferSize(0), m_writeBufferSize(0), m_readDirSize(0) { qCDebug(LOG_KIO_NFS) << "NFS3::NFS3"; clnt_timeout.tv_sec = 20; clnt_timeout.tv_usec = 0; } NFSProtocolV3::~NFSProtocolV3() { closeConnection(); } bool NFSProtocolV3::isCompatible(bool& connectionError) { qCDebug(LOG_KIO_NFS); int ret = -1; - CLIENT* client = NULL; + CLIENT* client = nullptr; int sock = 0; if (NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, client, sock) == 0) { timeval check_timeout; check_timeout.tv_sec = 20; check_timeout.tv_usec = 0; // Check if the NFS version is compatible ret = clnt_call(client, NFSPROC3_NULL, - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_void, nullptr, + (xdrproc_t) xdr_void, nullptr, check_timeout); connectionError = false; } else { qCDebug(LOG_KIO_NFS) << "openConnection failed"; connectionError = true; } if (sock != -1) { ::close(sock); } - if (client != NULL) { + if (client != nullptr) { CLNT_DESTROY(client); } qCDebug(LOG_KIO_NFS) << ret; return (ret == RPC_SUCCESS); } bool NFSProtocolV3::isConnected() const { - return (m_nfsClient != 0); + return (m_nfsClient != nullptr); } void NFSProtocolV3::closeConnection() { qCDebug(LOG_KIO_NFS); // Unmount all exported dirs(if any) - if (m_mountClient != 0) { + if (m_mountClient != nullptr) { clnt_call(m_mountClient, MOUNTPROC3_UMNTALL, - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_void, nullptr, + (xdrproc_t) xdr_void, nullptr, clnt_timeout); } if (m_mountSock >= 0) { ::close(m_mountSock); m_mountSock = -1; } if (m_nfsSock >= 0) { ::close(m_nfsSock); m_nfsSock = -1; } - if (m_mountClient != 0) { + if (m_mountClient != nullptr) { CLNT_DESTROY(m_mountClient); - m_mountClient = 0; + m_mountClient = nullptr; } - if (m_nfsClient != 0) { + if (m_nfsClient != nullptr) { CLNT_DESTROY(m_nfsClient); - m_nfsClient = 0; + m_nfsClient = nullptr; } } NFSFileHandle NFSProtocolV3::lookupFileHandle(const QString& path) { int rpcStatus; LOOKUP3res res; if (lookupHandle(path, rpcStatus, res)) { NFSFileHandle fh = res.LOOKUP3res_u.resok.object; // Is it a link? Get the link target. if (res.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) { READLINK3args readLinkArgs; memset(&readLinkArgs, 0, sizeof(readLinkArgs)); fh.toFH(readLinkArgs.symlink); char dataBuffer[NFS3_MAXPATHLEN]; READLINK3res readLinkRes; memset(&readLinkRes, 0, sizeof(readLinkRes)); readLinkRes.READLINK3res_u.resok.data = dataBuffer; int rpcStatus = clnt_call(m_nfsClient, NFSPROC3_READLINK, (xdrproc_t) xdr_READLINK3args, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_READLINK3res, reinterpret_cast(&readLinkRes), clnt_timeout); if (rpcStatus == RPC_SUCCESS && readLinkRes.status == NFS3_OK) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(QFileInfo(path).path(), linkDest).absoluteFilePath(); } LOOKUP3res linkRes; if (lookupHandle(linkPath, rpcStatus, linkRes)) { // It's a link, so return the target file handle, and add the link source to it. NFSFileHandle linkFh = linkRes.LOOKUP3res_u.resok.object; linkFh.setLinkSource(res.LOOKUP3res_u.resok.object); qCDebug(LOG_KIO_NFS) << "Found target -" << linkPath; return linkFh; } } // If we have reached this point the file is a link, but we failed to get the target. fh.setBadLink(); qCDebug(LOG_KIO_NFS) << path << "is an invalid link!!"; } return fh; } return NFSFileHandle(); } /* Open connection connects to the mount daemon on the server side. In order to do this it needs authentication and calls auth_unix_create(). Then it asks the mount daemon for the exported shares. Then it tries to mount all these shares. If this succeeded for at least one of them, a client for the nfs daemon is created. */ void NFSProtocolV3::openConnection() { qCDebug(LOG_KIO_NFS) << m_currentHost; // Destroy the old connection first closeConnection(); int connErr; if ((connErr = NFSProtocol::openConnection(m_currentHost, MOUNT_PROGRAM, MOUNT_V3, m_mountClient, m_mountSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); return; } exports3 exportlist; memset(&exportlist, 0, sizeof(exportlist)); int clnt_stat = clnt_call(m_mountClient, MOUNTPROC3_EXPORT, - (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_void, nullptr, (xdrproc_t) xdr_exports3, reinterpret_cast(&exportlist), clnt_timeout); if (!checkForError(clnt_stat, 0, m_currentHost.toLatin1())) { closeConnection(); return; } int exportsCount = 0; QStringList failList; mountres3 fhStatus; - for (; exportlist != 0; exportlist = exportlist->ex_next, exportsCount++) { + for (; exportlist != nullptr; exportlist = exportlist->ex_next, exportsCount++) { memset(&fhStatus, 0, sizeof(fhStatus)); clnt_stat = clnt_call(m_mountClient, MOUNTPROC3_MNT, (xdrproc_t) xdr_dirpath3, reinterpret_cast(&exportlist->ex_dir), (xdrproc_t) xdr_mountres3, reinterpret_cast(&fhStatus), clnt_timeout); if (fhStatus.fhs_status == 0) { QString fname = QFileInfo(QDir("/"), exportlist->ex_dir).filePath(); // Check if the dir is already exported if (NFSProtocol::isExportedDir(fname)) { continue; } addFileHandle(fname, static_cast(fhStatus.mountres3_u.mountinfo.fhandle)); addExportedDir(fname); } else { failList.append(exportlist->ex_dir); } } if (failList.size() > 0) { m_slave->error(KIO::ERR_COULD_NOT_MOUNT, i18n("Failed to mount %1", failList.join(", "))); // All exports failed to mount, fail if (failList.size() == exportsCount) { closeConnection(); return; } } if ((connErr = NFSProtocol::openConnection(m_currentHost, NFSPROG, NFSVERS, m_nfsClient, m_nfsSock)) != 0) { closeConnection(); m_slave->error(connErr, m_currentHost); } m_slave->connected(); qCDebug(LOG_KIO_NFS) << "openConnection succeeded"; } void NFSProtocolV3::listDir(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const QString path(url.path()); // Is it part of an exported(virtual) dir? if (isExportedDir(path)) { qCDebug(LOG_KIO_NFS) << "Listing virtual dir" << path; QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(/mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith(QDir::separator())) { name = name.mid(1); } if (name.indexOf(QDir::separator()) != -1) { name.truncate(name.indexOf(QDir::separator())); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << "Found " << (*it) << "in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); // There doesn't seem to be an invalid link error code in KIO, so this will have to do. if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } // Get the preferred read dir size from the server if (m_readDirSize == 0) { initPreferredSizes(fh); } READDIRPLUS3args listargs; memset(&listargs, 0, sizeof(listargs)); listargs.dircount = m_readDirSize; listargs.maxcount = sizeof(entryplus3) * m_readDirSize; // Not really sure what this should be set to. fh.toFH(listargs.dir); READDIRPLUS3res listres; memset(&listres, 0, sizeof(listres)); - entryplus3* lastEntry = 0; + entryplus3* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received. - if (lastEntry != 0) { + if (lastEntry != nullptr) { listargs.cookie = lastEntry->cookie; } int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READDIRPLUS, (xdrproc_t) xdr_READDIRPLUS3args, reinterpret_cast(&listargs), (xdrproc_t) xdr_READDIRPLUS3res, reinterpret_cast(&listres), clnt_timeout); // Not a supported call? Try the old READDIR method. if (listres.status == NFS3ERR_NOTSUPP) { listDirCompat(url); return; } // Do we have an error? There's not much more we can do but to abort at this point. if (!checkForError(clnt_stat, listres.status, path)) { return; } - for (entryplus3* dirEntry = listres.READDIRPLUS3res_u.resok.reply.entries; dirEntry != 0; dirEntry = dirEntry->nextentry) { + for (entryplus3* dirEntry = listres.READDIRPLUS3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name == QString(".") || dirEntry->name == QString("..")) { continue; } const QString& filePath = QFileInfo(QDir(path), dirEntry->name).filePath(); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, dirEntry->name); // Is it a symlink ? if (dirEntry->name_attributes.post_op_attr_u.attributes.type == NF3LNK) { int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; LOOKUP3res lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { GETATTR3res attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.LOOKUP3res_u.resok.object; linkFH.setLinkSource(dirEntry->name_handle.post_op_fh3_u.handle); completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } } if (badLink) { linkFH = dirEntry->name_handle.post_op_fh3_u.handle; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } } else { addFileHandle(filePath, static_cast(dirEntry->name_handle.post_op_fh3_u.handle)); completeUDSEntry(entry, dirEntry->name_attributes.post_op_attr_u.attributes); } m_slave->listEntry(entry); lastEntry = dirEntry; } - } while (listres.READDIRPLUS3res_u.resok.reply.entries != NULL && !listres.READDIRPLUS3res_u.resok.reply.eof); + } while (listres.READDIRPLUS3res_u.resok.reply.entries != nullptr && !listres.READDIRPLUS3res_u.resok.reply.eof); m_slave->finished(); } void NFSProtocolV3::listDirCompat(const QUrl& url) { // We should always be connected if it reaches this point, // but better safe than sorry! if (!isConnected()) { return; } if (url.isEmpty()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, url.path()); } const QString path(url.path()); // Is it part of an exported (virtual) dir? if (NFSProtocol::isExportedDir(path)) { QStringList virtualList; for (QStringList::const_iterator it = getExportedDirs().constBegin(); it != getExportedDirs().constEnd(); ++it) { // When an export is multiple levels deep(mnt/nfs for example) we only // want to display one level at a time. QString name = (*it); name = name.remove(0, path.length()); if (name.startsWith('/')) { name = name.mid(1); } if (name.indexOf('/') != -1) { name.truncate(name.indexOf('/')); } if (!virtualList.contains(name)) { virtualList.append(name); } } for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) { qCDebug(LOG_KIO_NFS) << "Found " << (*it) << "in exported dir"; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); createVirtualDirEntry(entry); m_slave->listEntry(entry); } m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } QStringList filesToList; READDIR3args listargs; memset(&listargs, 0, sizeof(listargs)); listargs.count = m_readDirSize; fh.toFH(listargs.dir); READDIR3res listres; - entry3* lastEntry = 0; + entry3* lastEntry = nullptr; do { memset(&listres, 0, sizeof(listres)); // In case that we didn't get all entries we need to set the cookie to the last one we actually received - if (lastEntry != 0) { + if (lastEntry != nullptr) { listargs.cookie = lastEntry->cookie; } int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READDIR, (xdrproc_t) xdr_READDIR3args, reinterpret_cast(&listargs), (xdrproc_t) xdr_READDIR3res, reinterpret_cast(&listres), clnt_timeout); if (!checkForError(clnt_stat, listres.status, path)) { return; } - for (entry3* dirEntry = listres.READDIR3res_u.resok.reply.entries; dirEntry != 0; dirEntry = dirEntry->nextentry) { + for (entry3* dirEntry = listres.READDIR3res_u.resok.reply.entries; dirEntry != nullptr; dirEntry = dirEntry->nextentry) { if (dirEntry->name != QString(".") && dirEntry->name != QString("..")) { filesToList.append(QFile::decodeName(dirEntry->name)); } lastEntry = dirEntry; } } while (!listres.READDIR3res_u.resok.reply.eof); // Loop through all files, getting attributes and link path. KIO::UDSEntry entry; for (QStringList::const_iterator it = filesToList.constBegin(); it != filesToList.constEnd(); ++it) { QString filePath = QFileInfo(QDir(path), (*it)).filePath(); int rpcStatus; LOOKUP3res dirres; if (!lookupHandle(filePath, rpcStatus, dirres)) { qCDebug(LOG_KIO_NFS) << "Failed to lookup" << filePath << ", rpc:" << rpcStatus << ", nfs:" << dirres.status; // Try the next file instead of aborting continue; } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, (*it)); // Is it a symlink? if (dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3LNK) { int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(filePath, rpcStatus, readLinkRes, nameBuf)) { const QString linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); bool badLink = true; NFSFileHandle linkFH; if (isValidLink(path, linkDest)) { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(path, linkDest).absoluteFilePath(); } int rpcStatus; LOOKUP3res lookupRes; if (lookupHandle(linkPath, rpcStatus, lookupRes)) { GETATTR3res attrAndStat; if (getAttr(linkPath, rpcStatus, attrAndStat)) { badLink = false; linkFH = lookupRes.LOOKUP3res_u.resok.object; linkFH.setLinkSource(dirres.LOOKUP3res_u.resok.object); completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } } if (badLink) { linkFH = dirres.LOOKUP3res_u.resok.object; linkFH.setBadLink(); completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } addFileHandle(filePath, linkFH); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, i18n("Unknown target")); completeBadLinkUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } } else { addFileHandle(filePath, dirres.LOOKUP3res_u.resok.object); completeUDSEntry(entry, dirres.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes); } m_slave->listEntry(entry); } m_slave->finished(); } void NFSProtocolV3::stat(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); // We can't stat an exported dir, but we know it's a dir. if (isExportedDir(path)) { KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, path); createVirtualDirEntry(entry); m_slave->statEntry(entry); m_slave->finished(); return; } const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { qCDebug(LOG_KIO_NFS) << "File handle is invalid"; m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } int rpcStatus; GETATTR3res attrAndStat; if (!getAttr(path, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, path); return; } const QFileInfo fileInfo(path); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName()); // Is it a symlink? if (attrAndStat.GETATTR3res_u.resok.obj_attributes.type == NF3LNK) { qCDebug(LOG_KIO_NFS) << "It's a symlink"; //get the link dest QString linkDest; int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (readLink(path, rpcStatus, readLinkRes, nameBuf)) { linkDest = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); } else { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); m_slave->statEntry(entry); m_slave->finished(); return; } qCDebug(LOG_KIO_NFS) << "link dest is" << linkDest; entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkDest); if (!isValidLink(fileInfo.path(), linkDest)) { completeBadLinkUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } else { QString linkPath; if (QFileInfo(linkDest).isAbsolute()) { linkPath = linkDest; } else { linkPath = QFileInfo(fileInfo.path(), linkDest).absoluteFilePath(); } int rpcStatus; GETATTR3res attrAndStat; if (!getAttr(linkPath, rpcStatus, attrAndStat)) { checkForError(rpcStatus, attrAndStat.status, linkPath); return; } completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } } else { completeUDSEntry(entry, attrAndStat.GETATTR3res_u.resok.obj_attributes); } m_slave->statEntry(entry); m_slave->finished(); } void NFSProtocolV3::setHost(const QString& host) { qCDebug(LOG_KIO_NFS) << host; if (host.isEmpty()) { m_slave->error(KIO::ERR_UNKNOWN_HOST, QString()); return; } // No need to update if the host hasn't changed if (host == m_currentHost) { return; } m_currentHost = host; closeConnection(); } void NFSProtocolV3::mkdir(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } MKDIR3args createArgs; memset(&createArgs, 0, sizeof(createArgs)); fh.toFH(createArgs.where.dir); QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); createArgs.where.name = tmpName.data(); createArgs.attributes.mode.set_it = true; if (permissions == -1) { createArgs.attributes.mode.set_mode3_u.mode = 0755; } else { createArgs.attributes.mode.set_mode3_u.mode = permissions; } MKDIR3res dirres; memset(&dirres, 0, sizeof(dirres)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_MKDIR, (xdrproc_t) xdr_MKDIR3args, reinterpret_cast(&createArgs), (xdrproc_t) xdr_MKDIR3res, reinterpret_cast(&dirres), clnt_timeout); if (!checkForError(clnt_stat, dirres.status, path)) { return; } m_slave->finished(); } void NFSProtocolV3::del(const QUrl& url, bool/* isfile*/) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(QFileInfo(path).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } int rpcStatus; REMOVE3res res; if (!remove(path, rpcStatus, res)) { checkForError(rpcStatus, res.status, path); return; } m_slave->finished(); } void NFSProtocolV3::chmod(const QUrl& url, int permissions) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); if (isExportedDir(path)) { m_slave->error(KIO::ERR_ACCESS_DENIED, path); return; } sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mode.set_it = true; attributes.mode.set_mode3_u.mode = permissions; int rpcStatus; SETATTR3res setAttrRes; if (!setAttr(path, attributes, rpcStatus, setAttrRes)) { checkForError(rpcStatus, setAttrRes.status, path); return; } m_slave->finished(); } void NFSProtocolV3::get(const QUrl& url) { qCDebug(LOG_KIO_NFS) << url; const QString path(url.path()); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid() || fh.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, path); return; } // Get the optimal read buffer size. if (m_readBufferSize == 0) { initPreferredSizes(fh); } READ3args readArgs; memset(&readArgs, 0, sizeof(readArgs)); fh.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = m_readBufferSize; READ3res readRes; memset(&readRes, 0, sizeof(readRes)); readRes.READ3res_u.resok.data.data_len = m_readBufferSize; readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize]; // Most likely indicates out of memory if (!readRes.READ3res_u.resok.data.data_val) { m_slave->error(KIO::ERR_OUT_OF_MEMORY, path); return; } bool validRead = false; bool hasError = false; int read = 0; QByteArray readBuffer; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); // We are trying to read a directory, fail quietly if (readRes.status == NFS3ERR_ISDIR) { break; } if (!checkForError(clnt_stat, readRes.status, path)) { hasError = true; break; } read = readRes.READ3res_u.resok.count; readBuffer.setRawData(readRes.READ3res_u.resok.data.data_val, read); if (readArgs.offset == 0) { const QMimeDatabase db; const QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), readBuffer); m_slave->mimeType(type.name()); m_slave->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size); } readArgs.offset += read; if (read > 0) { validRead = true; m_slave->data(readBuffer); m_slave->processedSize(readArgs.offset); } } while (read > 0); - if (readRes.READ3res_u.resok.data.data_val != NULL) { + if (readRes.READ3res_u.resok.data.data_val != nullptr) { delete [] readRes.READ3res_u.resok.data.data_val; } // Only send the read data to the slave if we have actually sent some. if (validRead) { m_slave->data(QByteArray()); m_slave->processedSize(readArgs.offset); } if (!hasError) { m_slave->finished(); } } void NFSProtocolV3::put(const QUrl& url, int _mode, KIO::JobFlags flags) { qCDebug(LOG_KIO_NFS) << url; const QString destPath(url.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); return; } NFSFileHandle destFH = getFileHandle(destPath); if (destFH.isBadLink()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, destPath); return; } // the file exists and we don't want to overwrite if (!destFH.isInvalid() && ((flags & KIO::Overwrite) == 0)) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Get the optimal write buffer size if (m_writeBufferSize == 0) { initPreferredSizes(destFH); } int rpcStatus; CREATE3res createRes; if (!create(destPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, destPath); return; } // We created the file successfully. destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; int result; WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.offset = 0; writeArgs.stable = FILE_SYNC; WRITE3res writeRes; memset(&writeRes, 0, sizeof(writeRes)); // Loop until we get 0 (end of data). int bytesWritten = 0; bool error = false; do { QByteArray buffer; m_slave->dataReq(); result = m_slave->readData(buffer); if (result > 0) { char* data = buffer.data(); uint32 bytesToWrite = buffer.size(); int writeNow(0); do { if (bytesToWrite > m_writeBufferSize) { writeNow = m_writeBufferSize; } else { writeNow = bytesToWrite; } writeArgs.data.data_val = data; writeArgs.data.data_len = writeNow; writeArgs.count = writeNow; int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeNow = writeRes.WRITE3res_u.resok.count; bytesWritten += writeNow; writeArgs.offset = bytesWritten; data = data + writeNow; bytesToWrite -= writeNow; } while (bytesToWrite > 0); } if (error) { break; } } while (result > 0); if (!error) { m_slave->finished(); } } void NFSProtocolV3::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << dest; const QString srcPath(src.path()); if (isExportedDir(srcPath)) { m_slave->error(KIO::ERR_CANNOT_RENAME, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(destPath)) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; RENAME3res res; if (!rename(srcPath, destPath, rpcStatus, res)) { checkForError(rpcStatus, res.status, destPath); return; } m_slave->finished(); } void NFSProtocolV3::copySame(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); if (isExportedDir(QFileInfo(srcPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, srcPath); return; } const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { //get the link dest int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString linkPath = QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data); SYMLINK3res linkRes; if (!symLink(linkPath, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes.status, linkPath); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bPartExists) { int rpcStatus; LOOKUP3res partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) { if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size); if (bResume) { resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Create the file if we are not resuming a parted transfer, // or if we are not using part files(bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; CREATE3res createRes; if (!create(createPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, createPath); return; } destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Check what buffer size we should use, always use the smallest one. const int bufferSize = (m_readBufferSize < m_writeBufferSize) ? m_readBufferSize : m_writeBufferSize; WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.offset = 0; writeArgs.data.data_val = new char[bufferSize]; writeArgs.stable = FILE_SYNC; READ3args readArgs; memset(&readArgs, 0, sizeof(readArgs)); srcFH.toFH(readArgs.file); readArgs.offset = 0; readArgs.count = bufferSize; if (bResume) { writeArgs.offset = resumeOffset; readArgs.offset = resumeOffset; } READ3res readRes; readRes.READ3res_u.resok.data.data_val = writeArgs.data.data_val; WRITE3res writeRes; memset(&writeRes, 0, sizeof(WRITE3res)); bool error = false; int bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, srcPath)) { error = true; break; } bytesRead = readRes.READ3res_u.resok.data.data_len; // We should only send out the total size and mimetype at the start of the transfer if (readArgs.offset == 0 || (bResume && writeArgs.offset == resumeOffset)) { QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(writeArgs.data.data_val, bytesRead)); m_slave->mimeType(type.name()); m_slave->totalSize(readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size); } if (bytesRead > 0) { readArgs.offset += bytesRead; writeArgs.count = bytesRead; writeArgs.data.data_len = bytesRead; clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(readArgs.offset); } } while (bytesRead > 0); delete [] writeArgs.data.data_val; if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore modification time int rpcStatus; GETATTR3res attrRes; if (getAttr(srcPath, rpcStatus, attrRes)) { sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mtime.set_it = SET_TO_CLIENT_TIME; attributes.mtime.set_mtime_u.mtime.seconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.seconds; attributes.mtime.set_mtime_u.mtime.nseconds = attrRes.GETATTR3res_u.resok.obj_attributes.mtime.nseconds; SETATTR3res attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status; } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV3::copyFrom(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; const QString srcPath(src.path()); const NFSFileHandle srcFH = getFileHandle(srcPath); if (srcFH.isInvalid()) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); // The file exists and we don't want to overwrite. if (QFile::exists(destPath) && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. if (srcFH.isLink()) { qCDebug(LOG_KIO_NFS) << "Is a link"; //get the link dest int rpcStatus; READLINK3res readLinkRes; char nameBuf[NFS3_MAXPATHLEN]; if (!readLink(srcPath, rpcStatus, readLinkRes, nameBuf)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } QFile::link(QString::fromLocal8Bit(readLinkRes.READLINK3res_u.resok.data), destPath); m_slave->finished(); return; } if (m_readBufferSize == 0) { initPreferredSizes(srcFH); } unsigned int resumeOffset = 0; bool bResume = false; const QFileInfo partInfo(destPath + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { m_slave->error(KIO::ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = m_slave->canResume(partInfo.size()); resumeOffset = partInfo.size(); } if (bPartExists && !bResume) { QFile::remove(partInfo.absoluteFilePath()); } QFile::OpenMode openMode; QString outFileName; if (bResume) { outFileName = partInfo.absoluteFilePath(); openMode = QFile::WriteOnly | QFile::Append; } else { outFileName = (bMarkPartial ? partInfo.absoluteFilePath() : destPath); openMode = QFile::WriteOnly | QFile::Truncate; } QFile destFile(outFileName); if (!bResume) { QFile::Permissions perms; if (_mode == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(_mode | QFile::WriteOwner); } destFile.setPermissions(perms); } if (!destFile.open(openMode)) { switch (destFile.error()) { case QFile::OpenError: if (bResume) { m_slave->error(KIO::ERR_CANNOT_RESUME, destPath); } else { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); } break; case QFile::PermissionsError: m_slave->error(KIO::ERR_WRITE_ACCESS_DENIED, destPath); break; default: m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, destPath); break; } return; } READ3args readArgs; srcFH.toFH(readArgs.file); if (bResume) { readArgs.offset = resumeOffset; } else { readArgs.offset = 0; } readArgs.count = m_readBufferSize; READ3res readRes; memset(&readRes, 0, sizeof(readres)); readRes.READ3res_u.resok.data.data_val = new char[m_readBufferSize]; readRes.READ3res_u.resok.data.data_len = m_readBufferSize; bool error = false; unsigned long bytesToRead = 0, bytesRead = 0; do { int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_READ, (xdrproc_t) xdr_READ3args, reinterpret_cast(&readArgs), (xdrproc_t) xdr_READ3res, reinterpret_cast(&readRes), clnt_timeout); if (!checkForError(clnt_stat, readRes.status, destPath)) { error = true; break; } bytesRead = readRes.READ3res_u.resok.count; if (readArgs.offset == 0 || (bResume && readArgs.offset == resumeOffset)) { bytesToRead = readRes.READ3res_u.resok.file_attributes.post_op_attr_u.attributes.size; m_slave->totalSize(bytesToRead); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(src.fileName(), QByteArray::fromRawData(readRes.READ3res_u.resok.data.data_val, bytesRead)); m_slave->mimeType(type.name()); } if (bytesRead > 0) { readArgs.offset += bytesRead; if (destFile.write(readRes.READ3res_u.resok.data.data_val, bytesRead) < 0) { m_slave->error(KIO::ERR_COULD_NOT_WRITE, destPath); error = true; break; } m_slave->processedSize(readArgs.offset); } } while (readArgs.offset < bytesToRead); delete [] readRes.READ3res_u.resok.data.data_val; // Close the file so we can modify the modification time later. destFile.close(); if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep const int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(partInfo.absoluteFilePath()); } } } else { // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); if (QFile::exists(destPath)) { QFile::remove(destPath); } if (!QFile::rename(sPart, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << sPart << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf; utbuf.actime = QFileInfo(destPath).lastRead().toTime_t(); // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(destPath).constData(), &utbuf); } } qCDebug(LOG_KIO_NFS) << "Copied" << readArgs.offset << "bytes of data"; m_slave->processedSize(readArgs.offset); m_slave->finished(); } } void NFSProtocolV3::copyTo(const QUrl& src, const QUrl& dest, int _mode, KIO::JobFlags _flags) { qCDebug(LOG_KIO_NFS) << src << "to" << dest; // The source does not exist, how strange const QString srcPath(src.path()); if (!QFile::exists(srcPath)) { m_slave->error(KIO::ERR_DOES_NOT_EXIST, srcPath); return; } const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } // The file exists and we don't want to overwrite. if (!getFileHandle(destPath).isInvalid() && (_flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } // Is it a link? No need to copy the data then, just copy the link destination. const QString symlinkTarget = QFile::symLinkTarget(srcPath); if (!symlinkTarget.isEmpty()) { int rpcStatus; SYMLINK3res linkRes; if (!symLink(symlinkTarget, destPath, rpcStatus, linkRes)) { checkForError(rpcStatus, linkRes.status, symlinkTarget); return; } m_slave->finished(); return; } unsigned long resumeOffset = 0; bool bResume = false; const QString partFilePath = destPath + QLatin1String(".part"); const NFSFileHandle partFH = getFileHandle(partFilePath); const bool bPartExists = !partFH.isInvalid(); const bool bMarkPartial = m_slave->config()->readEntry("MarkPartial", true); if (bPartExists) { int rpcStatus; LOOKUP3res partRes; if (lookupHandle(partFilePath, rpcStatus, partRes)) { if (bMarkPartial && partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size > 0) { if (partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type == NF3DIR) { m_slave->error(KIO::ERR_IS_DIRECTORY, partFilePath); return; } bResume = m_slave->canResume(partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size); if (bResume) { resumeOffset = partRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.size; } } } // Remove the part file if we are not resuming if (!bResume) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } // Open the source file QFile srcFile(srcPath); if (!srcFile.open(QIODevice::ReadOnly)) { m_slave->error(KIO::ERR_CANNOT_OPEN_FOR_READING, srcPath); return; } // Create the file if we are not resuming a parted transfer, // or if we are not using part files (bResume is false in that case) NFSFileHandle destFH; if (!bResume) { QString createPath; if (bMarkPartial) { createPath = partFilePath; } else { createPath = destPath; } int rpcStatus; CREATE3res createRes; if (!create(createPath, _mode, rpcStatus, createRes)) { checkForError(rpcStatus, createRes.status, createPath); return; } destFH = createRes.CREATE3res_u.resok.obj.post_op_fh3_u.handle; } else { // Since we are resuming it's implied that we are using a part file, // which should exist at this point. destFH = getFileHandle(partFilePath); qCDebug(LOG_KIO_NFS) << "Resuming old transfer"; } // Send the total size to the slave. m_slave->totalSize(srcFile.size()); // Get the optimal write buffer size if (m_writeBufferSize == 0) { initPreferredSizes(destFH); } // Set up write arguments. WRITE3args writeArgs; memset(&writeArgs, 0, sizeof(writeArgs)); destFH.toFH(writeArgs.file); writeArgs.data.data_val = new char[m_writeBufferSize]; writeArgs.stable = FILE_SYNC; if (bResume) { writeArgs.offset = resumeOffset; } else { writeArgs.offset = 0; } WRITE3res writeRes; memset(&writeRes, 0 , sizeof(writeRes)); bool error = false; int bytesRead = 0; do { memset(writeArgs.data.data_val, 0, m_writeBufferSize); bytesRead = srcFile.read(writeArgs.data.data_val, m_writeBufferSize); if (bytesRead < 0) { m_slave->error(KIO::ERR_COULD_NOT_READ, srcPath); error = true; break; } if (bytesRead > 0) { writeArgs.count = bytesRead; writeArgs.data.data_len = bytesRead; int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_WRITE, (xdrproc_t) xdr_WRITE3args, reinterpret_cast(&writeArgs), (xdrproc_t) xdr_WRITE3res, reinterpret_cast(&writeRes), clnt_timeout); if (!checkForError(clnt_stat, writeRes.status, destPath)) { error = true; break; } writeArgs.offset += bytesRead; m_slave->processedSize(writeArgs.offset); } } while (bytesRead > 0); delete [] writeArgs.data.data_val; if (error) { if (bMarkPartial) { // Remove the part file if it's smaller than the minimum keep size. const unsigned int size = m_slave->config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (writeArgs.offset < size) { if (!remove(partFilePath)) { qCDebug(LOG_KIO_NFS) << "Could not remove part file, ignoring..."; } } } } else { // Rename partial file to its original name. if (bMarkPartial) { // Remove the destination file(if it exists) if (!getFileHandle(destPath).isInvalid() && !remove(destPath)) { qCDebug(LOG_KIO_NFS) << "Could not remove destination file" << destPath << ", ignoring..."; } if (!rename(partFilePath, destPath)) { qCDebug(LOG_KIO_NFS) << "failed to rename" << partFilePath << "to" << destPath; m_slave->error(KIO::ERR_CANNOT_RENAME_PARTIAL, partFilePath); return; } } // Restore the mtime on the file. const QString mtimeStr = m_slave->metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { sattr3 attributes; memset(&attributes, 0, sizeof(attributes)); attributes.mtime.set_it = SET_TO_CLIENT_TIME; attributes.mtime.set_mtime_u.mtime.seconds = dt.toTime_t(); attributes.mtime.set_mtime_u.mtime.nseconds = attributes.mtime.set_mtime_u.mtime.seconds * 1000000000ULL; int rpcStatus; SETATTR3res attrSetRes; if (!setAttr(destPath, attributes, rpcStatus, attrSetRes)) { qCDebug(LOG_KIO_NFS) << "Failed to restore mtime, ignoring..." << rpcStatus << attrSetRes.status; } } } qCDebug(LOG_KIO_NFS) << "Copied" << writeArgs.offset << "bytes of data"; m_slave->processedSize(writeArgs.offset); m_slave->finished(); } } void NFSProtocolV3::symlink(const QString& target, const QUrl& dest, KIO::JobFlags flags) { const QString destPath(dest.path()); if (isExportedDir(QFileInfo(destPath).path())) { m_slave->error(KIO::ERR_ACCESS_DENIED, destPath); return; } if (!getFileHandle(destPath).isInvalid() && (flags & KIO::Overwrite) == 0) { m_slave->error(KIO::ERR_FILE_ALREADY_EXIST, destPath); return; } int rpcStatus; SYMLINK3res res; if (!symLink(target, destPath, rpcStatus, res)) { checkForError(rpcStatus, res.status, destPath); return; } m_slave->finished(); } void NFSProtocolV3::initPreferredSizes(const NFSFileHandle& fh) { FSINFO3args fsArgs; memset(&fsArgs, 0, sizeof(fsArgs)); fh.toFH(fsArgs.fsroot); FSINFO3res fsRes; memset(&fsRes, 0, sizeof(fsRes)); int clnt_stat = clnt_call(m_nfsClient, NFSPROC3_FSINFO, (xdrproc_t) xdr_FSINFO3args, reinterpret_cast(&fsArgs), (xdrproc_t) xdr_FSINFO3res, reinterpret_cast(&fsRes), clnt_timeout); if (clnt_stat == RPC_SUCCESS && fsRes.status == NFS3_OK) { m_writeBufferSize = fsRes.FSINFO3res_u.resok.wtpref; m_readBufferSize = fsRes.FSINFO3res_u.resok.rtpref; m_readDirSize = fsRes.FSINFO3res_u.resok.dtpref; } else { m_writeBufferSize = NFS3_MAXDATA; m_readBufferSize = NFS3_MAXDATA; m_readDirSize = NFS3_MAXDATA; } qCDebug(LOG_KIO_NFS) << "Preferred sizes - write" << m_writeBufferSize << ", read" << m_readBufferSize << ", read dir" << m_readDirSize; } bool NFSProtocolV3::create(const QString& path, int mode, int& rpcStatus, CREATE3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); CREATE3args args; memset(&args, 0, sizeof(args)); directoryFH.toFH(args.where.dir); args.where.name = tmpName.data(); args.how.createhow3_u.obj_attributes.mode.set_it = true; args.how.createhow3_u.obj_attributes.uid.set_it = true; args.how.createhow3_u.obj_attributes.gid.set_it = true; args.how.createhow3_u.obj_attributes.size.set_it = true; if (mode == -1) { args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = 0644; } else { args.how.createhow3_u.obj_attributes.mode.set_mode3_u.mode = mode; } args.how.createhow3_u.obj_attributes.uid.set_uid3_u.uid = geteuid(); args.how.createhow3_u.obj_attributes.gid.set_gid3_u.gid = getegid(); args.how.createhow3_u.obj_attributes.size.set_size3_u.size = 0; rpcStatus = clnt_call(m_nfsClient, NFSPROC3_CREATE, (xdrproc_t) xdr_CREATE3args, reinterpret_cast(&args), (xdrproc_t) xdr_CREATE3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::getAttr(const QString& path, int& rpcStatus, GETATTR3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle fileFH = getFileHandle(path); if (fileFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } GETATTR3args args; memset(&args, 0, sizeof(GETATTR3args)); fileFH.toFH(args.object); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_GETATTR, (xdrproc_t) xdr_GETATTR3args, reinterpret_cast(&args), (xdrproc_t) xdr_GETATTR3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::lookupHandle(const QString& path, int& rpcStatus, LOOKUP3res& result) { memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_ACCES; return false; } const QFileInfo fileInfo(path); const NFSFileHandle parentFH = getFileHandle(fileInfo.path()); if (parentFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); // do the rpc call LOOKUP3args args; memset(&args, 0, sizeof(args)); parentFH.toFH(args.what.dir); args.what.name = tmpName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_LOOKUP, (xdrproc_t) xdr_LOOKUP3args, reinterpret_cast(&args), (xdrproc_t) xdr_LOOKUP3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::readLink(const QString& path, int& rpcStatus, READLINK3res& result, char* dataBuffer) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } READLINK3args readLinkArgs; memset(&readLinkArgs, 0, sizeof(readLinkArgs)); if (fh.isLink() && !fh.isBadLink()) { fh.toFHLink(readLinkArgs.symlink); } else { fh.toFH(readLinkArgs.symlink); } result.READLINK3res_u.resok.data = dataBuffer; rpcStatus = clnt_call(m_nfsClient, NFSPROC3_READLINK, (xdrproc_t) xdr_READLINK3args, reinterpret_cast(&readLinkArgs), (xdrproc_t) xdr_READLINK3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::remove(const QString& path) { int rpcStatus; REMOVE3res result; return remove(path, rpcStatus, result); } bool NFSProtocolV3::remove(const QString& path, int& rpcStatus, REMOVE3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); if (!isConnected()) { result.status = NFS3ERR_PERM; return false; } const QFileInfo fileInfo(path); if (isExportedDir(fileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle directoryFH = getFileHandle(fileInfo.path()); if (directoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } int rpcLookupStatus; LOOKUP3res lookupRes; if (!lookupHandle(path, rpcLookupStatus, lookupRes)) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpName = QFile::encodeName(fileInfo.fileName()); REMOVE3args args; memset(&args, 0, sizeof(args)); directoryFH.toFH(args.object.dir); args.object.name = tmpName.data(); if (lookupRes.LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes.type != NF3DIR) { rpcStatus = clnt_call(m_nfsClient, NFSPROC3_REMOVE, (xdrproc_t) xdr_REMOVE3args, reinterpret_cast(&args), (xdrproc_t) xdr_REMOVE3res, reinterpret_cast(&result), clnt_timeout); } else { rpcStatus = clnt_call(m_nfsClient, NFSPROC3_RMDIR, (xdrproc_t) xdr_RMDIR3args, reinterpret_cast(&args), (xdrproc_t) xdr_RMDIR3res, reinterpret_cast(&result), clnt_timeout); } bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); if (ret) { // Remove it from the cache as well removeFileHandle(path); } return ret; } bool NFSProtocolV3::rename(const QString& src, const QString& dest) { int rpcStatus; RENAME3res result; return rename(src, dest, rpcStatus, result); } bool NFSProtocolV3::rename(const QString& src, const QString& dest, int& rpcStatus, RENAME3res& result) { qCDebug(LOG_KIO_NFS) << src << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const QFileInfo srcFileInfo(src); if (isExportedDir(srcFileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle srcDirectoryFH = getFileHandle(srcFileInfo.path()); if (srcDirectoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } const QFileInfo destFileInfo(dest); if (isExportedDir(destFileInfo.path())) { result.status = NFS3ERR_ACCES; return false; } const NFSFileHandle destDirectoryFH = getFileHandle(destFileInfo.path()); if (destDirectoryFH.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } RENAME3args args; memset(&args, 0, sizeof(args)); QByteArray srcByteName = QFile::encodeName(srcFileInfo.fileName()); srcDirectoryFH.toFH(args.from.dir); args.from.name = srcByteName.data(); QByteArray destByteName = QFile::encodeName(destFileInfo.fileName()); destDirectoryFH.toFH(args.to.dir); args.to.name = destByteName.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_RENAME, (xdrproc_t) xdr_RENAME3args, reinterpret_cast(&args), (xdrproc_t) xdr_RENAME3res, reinterpret_cast(&result), clnt_timeout); bool ret = (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); if (ret) { // Can we actually find the new handle? int lookupStatus; LOOKUP3res lookupRes; if (lookupHandle(dest, lookupStatus, lookupRes)) { // Remove the old file, and add the new one removeFileHandle(src); addFileHandle(dest, lookupRes.LOOKUP3res_u.resok.object); } } return ret; } bool NFSProtocolV3::setAttr(const QString& path, const sattr3& attributes, int& rpcStatus, SETATTR3res& result) { qCDebug(LOG_KIO_NFS) << path; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); const NFSFileHandle fh = getFileHandle(path); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } SETATTR3args setAttrArgs; memset(&setAttrArgs, 0, sizeof(setAttrArgs)); fh.toFH(setAttrArgs.object); memcpy(&setAttrArgs.new_attributes, &attributes, sizeof(attributes)); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_SETATTR, (xdrproc_t) xdr_SETATTR3args, reinterpret_cast(&setAttrArgs), (xdrproc_t) xdr_SETATTR3res, reinterpret_cast(&result), clnt_timeout); return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } bool NFSProtocolV3::symLink(const QString& target, const QString& dest, int& rpcStatus, SYMLINK3res& result) { qCDebug(LOG_KIO_NFS) << target << dest; memset(&rpcStatus, 0, sizeof(int)); memset(&result, 0, sizeof(result)); // Remove dest first, we don't really care about the return value at this point, // the symlink call will fail if dest was not removed correctly. remove(dest); const QFileInfo fileInfo(dest); const NFSFileHandle fh = getFileHandle(fileInfo.path()); if (fh.isInvalid()) { result.status = NFS3ERR_NOENT; return false; } QByteArray tmpStr = QFile::encodeName(fileInfo.fileName()); QByteArray tmpStr2 = QFile::encodeName(target); SYMLINK3args symLinkArgs; memset(&symLinkArgs, 0, sizeof(symLinkArgs)); fh.toFH(symLinkArgs.where.dir); symLinkArgs.where.name = tmpStr.data(); symLinkArgs.symlink.symlink_data = tmpStr2.data(); rpcStatus = clnt_call(m_nfsClient, NFSPROC3_SYMLINK, (xdrproc_t) xdr_SYMLINK3args, reinterpret_cast(&symLinkArgs), (xdrproc_t) xdr_SYMLINK3res, reinterpret_cast(&result), clnt_timeout); // Add the new handle to the cache NFSFileHandle destFH = getFileHandle(dest); if (!destFH.isInvalid()) { addFileHandle(dest, destFH); } return (rpcStatus == RPC_SUCCESS && result.status == NFS3_OK); } void NFSProtocolV3::completeUDSEntry(KIO::UDSEntry& entry, const fattr3& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, attributes.size); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); // Some servers still send the file type information in the mode, even though // the reference specifies NFSv3 shouldn't, so we need to work around that here. // Not sure this is the best way to do it, but it works. if (attributes.mode > 0777) { entry.insert(KIO::UDSEntry::UDS_ACCESS, (attributes.mode & 07777)); } else { entry.insert(KIO::UDSEntry::UDS_ACCESS, attributes.mode); } unsigned int type; switch (attributes.type) { case NF3DIR: type = S_IFDIR; break; case NF3BLK: type = S_IFBLK; break; case NF3CHR: type = S_IFCHR; break; case NF3LNK: type = S_IFLNK; break; case NF3SOCK: type = S_IFSOCK; break; case NF3FIFO: type = S_IFIFO; break; default: type = S_IFREG; break; } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); QString str; const uid_t uid = attributes.uid; if (!m_usercache.contains(uid)) { struct passwd* user = getpwuid(uid); if (user) { m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); str = user->pw_name; } else { str = QString::number(uid); } } else { str = m_usercache.value(uid); } entry.insert(KIO::UDSEntry::UDS_USER, str); const gid_t gid = attributes.gid; if (!m_groupcache.contains(gid)) { struct group* grp = getgrgid(gid); if (grp) { m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); str = grp->gr_name; } else { str = QString::number(gid); } } else { str = m_groupcache.value(gid); } entry.insert(KIO::UDSEntry::UDS_GROUP, str); } void NFSProtocolV3::completeBadLinkUDSEntry(KIO::UDSEntry& entry, const fattr3& attributes) { entry.insert(KIO::UDSEntry::UDS_SIZE, 0LL); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, attributes.mtime.seconds); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, attributes.atime.seconds); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, attributes.ctime.seconds); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRWXU | S_IRWXG | S_IRWXO); entry.insert(KIO::UDSEntry::UDS_USER, attributes.uid); entry.insert(KIO::UDSEntry::UDS_GROUP, attributes.gid); } diff --git a/settings/kio_settings.cpp b/settings/kio_settings.cpp index 6a43f1f2..ae196056 100644 --- a/settings/kio_settings.cpp +++ b/settings/kio_settings.cpp @@ -1,216 +1,216 @@ /* This file is part of the KDE project Copyright (C) 2003 Joseph Wenninger Copyright (C) 2008 David Faure 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 #include #include #include #include #include #include class SettingsProtocol : public KIO::SlaveBase { public: SettingsProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app); virtual ~SettingsProtocol(); virtual void get( const QUrl& url ); virtual void stat(const QUrl& url); virtual void listDir(const QUrl& url); private: void initSettingsData(); private: bool m_settingsDataLoaded; KService::List m_modules; QHash m_settingsServiceLookup; KService::List m_categories; QHash m_categoryLookup; }; extern "C" { Q_DECL_EXPORT int kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName("kio_settings"); qDebug() << "kdemain for settings kioslave"; SettingsProtocol slave(argv[1], argv[2], argv[3]); slave.dispatchLoop(); return 0; } } static void createFileEntry(KIO::UDSEntry& entry, const KService::Ptr& service) { entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, KIO::encodeFileName(service->desktopEntryName())); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, service->name()); // translated name entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(KIO::UDSEntry::UDS_ACCESS, 0500); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "application/x-desktop"); entry.insert(KIO::UDSEntry::UDS_SIZE, 0); entry.insert(KIO::UDSEntry::UDS_LOCAL_PATH, QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + service->entryPath())); - entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, time(0)); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, time(nullptr)); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, service->icon()); } static void createDirEntry(KIO::UDSEntry& entry, const QString& name, const QString& iconName) { entry.clear(); entry.insert( KIO::UDSEntry::UDS_NAME, name ); entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); entry.insert( KIO::UDSEntry::UDS_ACCESS, 0500 ); entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory" ); entry.insert( KIO::UDSEntry::UDS_ICON_NAME, iconName ); } SettingsProtocol::SettingsProtocol( const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) : SlaveBase( protocol, pool, app ), m_settingsDataLoaded(false) { } SettingsProtocol::~SettingsProtocol() { } void SettingsProtocol::initSettingsData() { if (m_settingsDataLoaded) return; // The code for settings:/ was inspired by kdebase/workspace/systemsettings/mainwindow.cpp readMenu(). m_modules = KServiceTypeTrader::self()->query("KCModule"); m_categories = KServiceTypeTrader::self()->query("SystemSettingsCategory"); for (int i = 0; i < m_categories.size(); ++i) { const KService::Ptr service = m_categories.at(i); const QString category = service->property("X-KDE-System-Settings-Category").toString(); m_categoryLookup.insert(category, service); } for (int i = 0; i < m_modules.size(); ++i) { const KService::Ptr service = m_modules.at(i); // Since modules have a unique name, we can just look them up by name, // no need to create a real hierarchical structure just for stat(). //const QString category = service->property("X-KDE-System-Settings-Parent-Category").toString(); m_settingsServiceLookup.insert(service->desktopEntryName(), service); } } void SettingsProtocol::stat(const QUrl& url) { initSettingsData(); const QString fileName = url.fileName(); qDebug() << fileName; KIO::UDSEntry entry; // Root dir? if (fileName.isEmpty()) { createDirEntry(entry, ".", "preferences-system"); statEntry(entry); finished(); return; } // Is it a category? QHash::const_iterator it = m_categoryLookup.constFind(fileName); if (it != m_categoryLookup.constEnd()) { const KService::Ptr service = it.value(); const QString parentCategory = service->property("X-KDE-System-Settings-Parent-Category").toString(); const QString category = service->property("X-KDE-System-Settings-Category").toString(); //qDebug() << "category" << service->desktopEntryName() << service->name() << "category=" << category << "parentCategory=" << parentCategory; createDirEntry(entry, category, service->icon()); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, service->name()); statEntry(entry); finished(); return; } else { // Is it a config module? it = m_settingsServiceLookup.constFind(fileName); if (it != m_settingsServiceLookup.constEnd()) { const KService::Ptr service = it.value(); createFileEntry(entry, service); statEntry(entry); finished(); return; } } error(KIO::ERR_DOES_NOT_EXIST, url.url()); } void SettingsProtocol::listDir(const QUrl& url) { initSettingsData(); const QString fileName = url.fileName(); if (!fileName.isEmpty() && !m_categoryLookup.contains(fileName)) { error(KIO::ERR_DOES_NOT_EXIST, fileName); return; } unsigned int count = 0; KIO::UDSEntry entry; // scan for any categories at this level and add them for (int i = 0; i < m_categories.size(); ++i) { const KService::Ptr service = m_categories.at(i); QString parentCategory = service->property("X-KDE-System-Settings-Parent-Category").toString(); QString category = service->property("X-KDE-System-Settings-Category").toString(); //qDebug() << "category" << service->desktopEntryName() << service->name() << "category=" << category << "parentCategory=" << parentCategory; if (parentCategory == fileName) { //KUrl dirUrl = url; //dirUrl.addPath(category); createDirEntry(entry, category, service->icon()); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, service->name()); listEntry(entry); ++count; } } // scan for any modules at this level and add them for (int i = 0; i < m_modules.size(); ++i) { const KService::Ptr service = m_modules.at(i); const QString category = service->property("X-KDE-System-Settings-Parent-Category").toString(); if (!fileName.isEmpty() && category == fileName) { createFileEntry(entry, service); listEntry(entry); ++count; } } totalSize(count); finished(); } void SettingsProtocol::get( const QUrl & url ) { KService::Ptr service = KService::serviceByDesktopName(url.fileName()); if (service && service->isValid()) { QUrl redirUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + service->entryPath())); redirection(redirUrl); finished(); } else { error( KIO::ERR_IS_DIRECTORY, url.toDisplayString() ); } } diff --git a/sftp/kio_sftp.cpp b/sftp/kio_sftp.cpp index b96bcdf2..0a823658 100644 --- a/sftp/kio_sftp.cpp +++ b/sftp/kio_sftp.cpp @@ -1,2290 +1,2290 @@ /* * Copyright (c) 2001 Lucas Fisher * Copyright (c) 2009 Andreas Schneider * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) 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_sftp.h" #include #include "kio_smtp_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KIO_SFTP_SPECIAL_TIMEOUT 30 #define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) // How big should each data packet be? Definitely not bigger than 64kb or // you will overflow the 2 byte size variable in a sftp packet. #define MAX_XFER_BUF_SIZE (60 * 1024) #define KIO_SFTP_DB 7120 // Maximum amount of data which can be sent from the KIOSlave in one chunk // see TransferJob::slotDataReq (max_size variable) for the value #define MAX_TRANSFER_SIZE (14 * 1024 * 1024) using namespace KIO; extern "C" { int Q_DECL_EXPORT kdemain( int argc, char **argv ) { QCoreApplication app(argc, argv); app.setApplicationName("kio_sftp"); qCDebug(KIO_SFTP_LOG) << "*** Starting kio_sftp "; if (argc != 4) { qCDebug(KIO_SFTP_LOG) << "Usage: kio_sftp protocol domain-socket1 domain-socket2"; exit(-1); } sftpProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); qCDebug(KIO_SFTP_LOG) << "*** kio_sftp Done"; return 0; } } // Converts SSH error into KIO error static int toKIOError (const int err) { switch (err) { case SSH_FX_OK: break; case SSH_FX_NO_SUCH_FILE: case SSH_FX_NO_SUCH_PATH: return KIO::ERR_DOES_NOT_EXIST; case SSH_FX_PERMISSION_DENIED: return KIO::ERR_ACCESS_DENIED; case SSH_FX_FILE_ALREADY_EXISTS: return KIO::ERR_FILE_ALREADY_EXIST; case SSH_FX_INVALID_HANDLE: return KIO::ERR_MALFORMED_URL; case SSH_FX_OP_UNSUPPORTED: return KIO::ERR_UNSUPPORTED_ACTION; case SSH_FX_BAD_MESSAGE: return KIO::ERR_UNKNOWN; default: return KIO::ERR_INTERNAL; } return 0; } // Writes 'len' bytes from 'buf' to the file handle 'fd'. static int writeToFile(int fd, const char *buf, size_t len) { while (len > 0) { ssize_t written = write(fd, buf, len); if (written >= 0) { buf += written; len -= written; continue; } switch(errno) { case EINTR: case EAGAIN: continue; case EPIPE: return ERR_CONNECTION_BROKEN; case ENOSPC: return ERR_DISK_FULL; default: return ERR_COULD_NOT_WRITE; } } return 0; } static int seekPos(int fd, KIO::fileoffset_t pos, int mode) { KIO::fileoffset_t offset = -1; while ((offset = QT_LSEEK(fd, pos, mode)) == EAGAIN); return offset; } static bool wasUsernameChanged(const QString& username, const KIO::AuthInfo& info) { QString loginName (username); // If username is empty, assume the current logged in username. Why ? // Because libssh's SSH_OPTIONS_USER will default to that when it is not // set and it won't be set unless the user explicitly typed a user user // name as part of the request URL. if (loginName.isEmpty()) { KUser u; loginName = u.loginName(); } return (loginName != info.username); } // The callback function for libssh static int auth_callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { - if (userdata == NULL) { + if (userdata == nullptr) { return -1; } sftpProtocol *slave = (sftpProtocol *) userdata; if (slave->auth_callback(prompt, buf, len, echo, verify, userdata) < 0) { return -1; } return 0; } static void log_callback(int priority, const char *function, const char *buffer, void *userdata) { - if (userdata == NULL) { + if (userdata == nullptr) { return; } sftpProtocol *slave = (sftpProtocol *) userdata; slave->log_callback(priority, function, buffer, userdata); } int sftpProtocol::auth_callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { // unused variables (void) echo; (void) verify; (void) userdata; QString errMsg; if (!mPublicKeyAuthInfo) { mPublicKeyAuthInfo = new KIO::AuthInfo; } else { errMsg = i18n("Incorrect or invalid passphrase"); } mPublicKeyAuthInfo->url.setScheme(QLatin1String("sftp")); mPublicKeyAuthInfo->url.setHost(mHost); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { mPublicKeyAuthInfo->url.setPort(mPort); } mPublicKeyAuthInfo->url.setUserName(mUsername); QUrl u (mPublicKeyAuthInfo->url); u.setPath(QString()); mPublicKeyAuthInfo->comment = u.url(); mPublicKeyAuthInfo->readOnly = true; mPublicKeyAuthInfo->prompt = QString::fromUtf8(prompt); mPublicKeyAuthInfo->keepPassword = false; // don't save passwords for public key, // that's the task of ssh-agent. mPublicKeyAuthInfo->setExtraField(QLatin1String("hide-username-line"), true); mPublicKeyAuthInfo->setModified(false); qCDebug(KIO_SFTP_LOG) << "Entering authentication callback, prompt=" << mPublicKeyAuthInfo->prompt; if (openPasswordDialogV2(*mPublicKeyAuthInfo, errMsg) != 0) { qCDebug(KIO_SFTP_LOG) << "User canceled public key passpharse dialog"; return -1; } strncpy(buf, mPublicKeyAuthInfo->password.toUtf8().constData(), len - 1); mPublicKeyAuthInfo->password.fill('x'); mPublicKeyAuthInfo->password.clear(); return 0; } void sftpProtocol::log_callback(int priority, const char *function, const char *buffer, void *userdata) { (void) userdata; qCDebug(KIO_SFTP_LOG) << "[" << function << "] (" << priority << ") " << buffer; } void sftpProtocol::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); } } int sftpProtocol::authenticateKeyboardInteractive(AuthInfo &info) { - int err = ssh_userauth_kbdint(mSession, NULL, NULL); + int err = ssh_userauth_kbdint(mSession, nullptr, nullptr); while (err == SSH_AUTH_INFO) { const QString name = QString::fromUtf8(ssh_userauth_kbdint_getname(mSession)); const QString instruction = QString::fromUtf8(ssh_userauth_kbdint_getinstruction(mSession)); const int n = ssh_userauth_kbdint_getnprompts(mSession); qCDebug(KIO_SFTP_LOG) << "name=" << name << " instruction=" << instruction << " prompts=" << n; for (int i = 0; i < n; ++i) { char echo; const char *answer = ""; const QString prompt = QString::fromUtf8(ssh_userauth_kbdint_getprompt(mSession, i, &echo)); qCDebug(KIO_SFTP_LOG) << "prompt=" << prompt << " echo=" << QString::number(echo); if (echo) { // See RFC4256 Section 3.3 User Interface KIO::AuthInfo infoKbdInt; infoKbdInt.url.setScheme("sftp"); infoKbdInt.url.setHost(mHost); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { infoKbdInt.url.setPort(mPort); } if (!name.isEmpty()) { infoKbdInt.caption = QString(i18n("SFTP Login") + " - " + name); } else { infoKbdInt.caption = i18n("SFTP Login"); } infoKbdInt.comment = "sftp://" + mUsername + "@" + mHost; QString newPrompt; if (!instruction.isEmpty()) { newPrompt = instruction + "

    "; } newPrompt.append(prompt); infoKbdInt.prompt = newPrompt; infoKbdInt.readOnly = false; infoKbdInt.keepPassword = false; if (openPasswordDialogV2(infoKbdInt, i18n("Use the username input field to answer this question.")) == 0) { qCDebug(KIO_SFTP_LOG) << "Got the answer from the password dialog"; answer = info.username.toUtf8().constData(); } if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { qCDebug(KIO_SFTP_LOG) << "An error occurred setting the answer: " << ssh_get_error(mSession); return SSH_AUTH_ERROR; } break; } else { if (prompt.startsWith(QLatin1String("password:"), Qt::CaseInsensitive)) { info.prompt = i18n("Please enter your password."); } else { info.prompt = prompt; } info.comment = info.url.url(); info.commentLabel = i18n("Site:"); info.setExtraField(QLatin1String("hide-username-line"), true); if (openPasswordDialogV2(info) == 0) { qCDebug(KIO_SFTP_LOG) << "Got the answer from the password dialog"; answer = info.password.toUtf8().constData(); } if (ssh_userauth_kbdint_setanswer(mSession, i, answer) < 0) { qCDebug(KIO_SFTP_LOG) << "An error occurred setting the answer: " << ssh_get_error(mSession); return SSH_AUTH_ERROR; } } } - err = ssh_userauth_kbdint(mSession, NULL, NULL); + err = ssh_userauth_kbdint(mSession, nullptr, nullptr); } return err; } void sftpProtocol::reportError(const QUrl &url, const int err) { qCDebug(KIO_SFTP_LOG) << "url = " << url << " - err=" << err; const int kioError = toKIOError(err); if (kioError) error(kioError, url.toDisplayString()); } bool sftpProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, short int details) { mode_t type; mode_t access; char *link; Q_ASSERT(entry.count() == 0); sftp_attributes sb = sftp_lstat(mSftp, path.constData()); - if (sb == NULL) { + if (sb == nullptr) { return false; } entry.insert(KIO::UDSEntry::UDS_NAME, filename); if (sb->type == SSH_FILEXFER_TYPE_SYMLINK) { entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); link = sftp_readlink(mSftp, path.constData()); - if (link == NULL) { + if (link == nullptr) { sftp_attributes_free(sb); return false; } entry.insert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(link)); delete link; // A symlink -> follow it only if details > 1 if (details > 1) { sftp_attributes sb2 = sftp_stat(mSftp, path.constData()); - if (sb2 == NULL) { + if (sb2 == nullptr) { // It is a link pointing to nowhere type = S_IFMT - 1; access = S_IRWXU | S_IRWXG | S_IRWXO; entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type); entry.insert( KIO::UDSEntry::UDS_ACCESS, access); entry.insert( KIO::UDSEntry::UDS_SIZE, 0LL ); goto notype; } sftp_attributes_free(sb); sb = sb2; } } switch (sb->type) { case SSH_FILEXFER_TYPE_REGULAR: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); break; case SSH_FILEXFER_TYPE_DIRECTORY: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case SSH_FILEXFER_TYPE_SYMLINK: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFLNK); break; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1); break; } access = sb->permissions & 07777; entry.insert(KIO::UDSEntry::UDS_ACCESS, access); entry.insert(KIO::UDSEntry::UDS_SIZE, sb->size); notype: if (details > 0) { if (sb->owner) { entry.insert(KIO::UDSEntry::UDS_USER, QString::fromUtf8(sb->owner)); } else { entry.insert(KIO::UDSEntry::UDS_USER, QString::number(sb->uid)); } if (sb->group) { entry.insert(KIO::UDSEntry::UDS_GROUP, QString::fromUtf8(sb->group)); } else { entry.insert(KIO::UDSEntry::UDS_GROUP, QString::number(sb->gid)); } entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, sb->atime); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, sb->mtime); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, sb->createtime); } sftp_attributes_free(sb); return true; } QString sftpProtocol::canonicalizePath(const QString &path) { qCDebug(KIO_SFTP_LOG) << "Path to canonicalize: " << path; QString cPath; - char *sPath = NULL; + char *sPath = nullptr; if (path.isEmpty()) { return cPath; } sPath = sftp_canonicalize_path(mSftp, path.toUtf8().constData()); - if (sPath == NULL) { + if (sPath == nullptr) { qCDebug(KIO_SFTP_LOG) << "Could not canonicalize path: " << path; return cPath; } cPath = QFile::decodeName(sPath); delete sPath; qCDebug(KIO_SFTP_LOG) << "Canonicalized path: " << cPath; return cPath; } sftpProtocol::sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : SlaveBase("kio_sftp", pool_socket, app_socket), - mConnected(false), mPort(-1), mSession(NULL), mSftp(NULL), mPublicKeyAuthInfo(0) { + mConnected(false), mPort(-1), mSession(nullptr), mSftp(nullptr), mPublicKeyAuthInfo(nullptr) { #ifndef Q_OS_WIN qCDebug(KIO_SFTP_LOG) << "pid = " << getpid(); qCDebug(KIO_SFTP_LOG) << "debug = " << getenv("KIO_SFTP_LOG_VERBOSITY"); #endif mCallbacks = (ssh_callbacks) malloc(sizeof(struct ssh_callbacks_struct)); - if (mCallbacks == NULL) { + if (mCallbacks == nullptr) { error(KIO::ERR_OUT_OF_MEMORY, i18n("Could not allocate callbacks")); return; } ZERO_STRUCTP(mCallbacks); mCallbacks->userdata = this; mCallbacks->auth_function = ::auth_callback; ssh_callbacks_init(mCallbacks); char *verbosity = getenv("KIO_SFTP_LOG_VERBOSITY"); - if (verbosity != NULL) { + if (verbosity != nullptr) { int level = atoi(verbosity); int rc; rc = ssh_set_log_level(level); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log verbosity.")); return; } rc = ssh_set_log_userdata(this); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log userdata.")); return; } rc = ssh_set_log_callback(::log_callback); if (rc != SSH_OK) { error(KIO::ERR_INTERNAL, i18n("Could not set log callback.")); return; } } } sftpProtocol::~sftpProtocol() { #ifndef Q_OS_WIN qCDebug(KIO_SFTP_LOG) << "pid = " << getpid(); #endif closeConnection(); delete mCallbacks; delete mPublicKeyAuthInfo; // for precaution /* cleanup and shut down cryto stuff */ ssh_finalize(); } void sftpProtocol::setHost(const QString& host, quint16 port, const QString& user, const QString& pass) { qCDebug(KIO_SFTP_LOG) << user << "@" << host << ":" << port; // Close connection if the request is to another server... if (host != mHost || port != mPort || user != mUsername || pass != mPassword) { closeConnection(); } mHost = host; mPort = port; mUsername = user; mPassword = pass; } bool sftpProtocol::sftpOpenConnection (const AuthInfo& info) { mSession = ssh_new(); - if (mSession == NULL) { + if (mSession == nullptr) { error(KIO::ERR_OUT_OF_MEMORY, i18n("Could not create a new SSH session.")); return false; } long timeout_sec = 30, timeout_usec = 0; qCDebug(KIO_SFTP_LOG) << "Creating the SSH session and setting options"; // Set timeout int rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT, &timeout_sec); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set a timeout.")); return false; } rc = ssh_options_set(mSession, SSH_OPTIONS_TIMEOUT_USEC, &timeout_usec); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set a timeout.")); return false; } // Don't use any compression rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_C_S, "none"); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set compression.")); return false; } rc = ssh_options_set(mSession, SSH_OPTIONS_COMPRESSION_S_C, "none"); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set compression.")); return false; } // Set host and port rc = ssh_options_set(mSession, SSH_OPTIONS_HOST, mHost.toUtf8().constData()); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set host.")); return false; } if (mPort > 0) { rc = ssh_options_set(mSession, SSH_OPTIONS_PORT, &mPort); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set port.")); return false; } } // Set the username if (!info.username.isEmpty()) { rc = ssh_options_set(mSession, SSH_OPTIONS_USER, info.username.toUtf8().constData()); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not set username.")); return false; } } // Read ~/.ssh/config - rc = ssh_options_parse_config(mSession, NULL); + rc = ssh_options_parse_config(mSession, nullptr); if (rc < 0) { error(KIO::ERR_INTERNAL, i18n("Could not parse the config file.")); return false; } ssh_set_callbacks(mSession, mCallbacks); qCDebug(KIO_SFTP_LOG) << "Trying to connect to the SSH server"; unsigned int effectivePort; if (mPort > 0) { effectivePort = mPort; } else { effectivePort = DEFAULT_SFTP_PORT; ssh_options_get_port(mSession, &effectivePort); } qCDebug(KIO_SFTP_LOG) << "username=" << mUsername << ", host=" << mHost << ", port=" << effectivePort; infoMessage(xi18n("Opening SFTP connection to host %1:%2", mHost, QString::number(effectivePort))); /* try to connect */ rc = ssh_connect(mSession); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return false; } return true; } void sftpProtocol::openConnection() { if (mConnected) { return; } if (mHost.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "openConnection(): Need hostname..."; error(KIO::ERR_UNKNOWN_HOST, QString()); return; } AuthInfo info; info.url.setScheme("sftp"); info.url.setHost(mHost); if ( mPort > 0 && mPort != DEFAULT_SFTP_PORT ) { info.url.setPort(mPort); } info.url.setUserName(mUsername); info.username = mUsername; // Check for cached authentication info if no password is specified... if (mPassword.isEmpty()) { qCDebug(KIO_SFTP_LOG) << "checking cache: info.username =" << info.username << ", info.url =" << info.url.toDisplayString(); checkCachedAuthentication(info); } else { info.password = mPassword; } // Start the ssh connection. QString msg; // msg for dialog box QString caption; // dialog box caption - unsigned char *hash = NULL; // the server hash + unsigned char *hash = nullptr; // the server hash ssh_key srv_pubkey; char *hexa; size_t hlen; int rc, state; // Attempt to start a ssh session and establish a connection with the server. if (!sftpOpenConnection(info)) { return; } qCDebug(KIO_SFTP_LOG) << "Getting the SSH server hash"; /* get the hash */ rc = ssh_get_publickey(mSession, &srv_pubkey); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen); ssh_key_free(srv_pubkey); if (rc < 0) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not create hash from server public key")); closeConnection(); return; } qCDebug(KIO_SFTP_LOG) << "Checking if the SSH server is known"; /* check the server public key hash */ state = ssh_is_server_known(mSession); switch (state) { case SSH_SERVER_KNOWN_OK: break; case SSH_SERVER_FOUND_OTHER: ssh_string_free_char((char *)hash); error(KIO::ERR_SLAVE_DEFINED, i18n("The host key for this server was " "not found, but another type of key exists.\n" "An attacker might change the default server key to confuse your " "client into thinking the key does not exist.\n" "Please contact your system administrator.\n%1", QString::fromUtf8(ssh_get_error(mSession)))); closeConnection(); return; case SSH_SERVER_KNOWN_CHANGED: hexa = ssh_get_hexa(hash, hlen); ssh_string_free_char((char *)hash); /* TODO print known_hosts file, port? */ error(KIO::ERR_SLAVE_DEFINED, i18n("The host key for the server %1 has changed.\n" "This could either mean that DNS SPOOFING is happening or the IP " "address for the host and its host key have changed at the same time.\n" "The fingerprint for the key sent by the remote host is:\n %2\n" "Please contact your system administrator.\n%3", mHost, QString::fromUtf8(hexa), QString::fromUtf8(ssh_get_error(mSession)))); ssh_string_free_char(hexa); closeConnection(); return; case SSH_SERVER_FILE_NOT_FOUND: case SSH_SERVER_NOT_KNOWN: hexa = ssh_get_hexa(hash, hlen); ssh_string_free_char((char *)hash); caption = i18n("Warning: Cannot verify host's identity."); msg = i18n("The authenticity of host %1 cannot be established.\n" "The key fingerprint is: %2\n" "Are you sure you want to continue connecting?", mHost, hexa); ssh_string_free_char(hexa); if (KMessageBox::Yes != messageBox(WarningYesNo, msg, caption)) { closeConnection(); error(KIO::ERR_USER_CANCELED, QString()); return; } /* write the known_hosts file */ qCDebug(KIO_SFTP_LOG) << "Adding server to known_hosts file."; if (ssh_write_knownhost(mSession) < 0) { error(KIO::ERR_USER_CANCELED, QString::fromUtf8(ssh_get_error(mSession))); closeConnection(); return; } break; case SSH_SERVER_ERROR: ssh_string_free_char((char *)hash); error(KIO::ERR_SLAVE_DEFINED, QString::fromUtf8(ssh_get_error(mSession))); return; } qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with the server"; // Try to login without authentication - rc = ssh_userauth_none(mSession, NULL); + rc = ssh_userauth_none(mSession, nullptr); if (rc == SSH_AUTH_ERROR) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // This NEEDS to be called after ssh_userauth_none() !!! int method = ssh_auth_list(mSession); if (rc != SSH_AUTH_SUCCESS && method == 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed. The server " "didn't send any authentication methods")); return; } // Try to authenticate with public key first if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PUBLICKEY)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with public key"; for(;;) { - rc = ssh_userauth_publickey_auto(mSession, NULL, NULL); + rc = ssh_userauth_publickey_auto(mSession, nullptr, nullptr); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); clearPubKeyAuthInfo(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } else if (rc != SSH_AUTH_DENIED || !mPublicKeyAuthInfo || !mPublicKeyAuthInfo->isModified()) { clearPubKeyAuthInfo(); break; } } } // Try to authenticate with GSSAPI if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_GSSAPI_MIC)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with GSSAPI"; rc = ssh_userauth_gssapi(mSession); if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Public key authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with keyboard interactive if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_INTERACTIVE)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with keyboard interactive"; AuthInfo info2 (info); rc = authenticateKeyboardInteractive(info2); if (rc == SSH_AUTH_SUCCESS) { info = info2; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Keyboard interactive authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } } // Try to authenticate with password if (rc != SSH_AUTH_SUCCESS && (method & SSH_AUTH_METHOD_PASSWORD)) { qCDebug(KIO_SFTP_LOG) << "Trying to authenticate with password"; info.caption = i18n("SFTP Login"); info.prompt = i18n("Please enter your username and password."); info.comment = info.url.url(); info.commentLabel = i18n("Site:"); bool isFirstLoginAttempt = true; for(;;) { if (!isFirstLoginAttempt || info.password.isEmpty()) { info.keepPassword = true; // make the "keep Password" check box visible to the user. info.setModified(false); QString username (info.username); const QString errMsg(isFirstLoginAttempt ? QString() : i18n("Incorrect username or password")); qCDebug(KIO_SFTP_LOG) << "Username:" << username << "first attempt?" << isFirstLoginAttempt << "error:" << errMsg; // Handle user canceled or dialog failed to open... int errCode = openPasswordDialogV2(info, errMsg); if (errCode != 0) { qCDebug(KIO_SFTP_LOG) << "User canceled password/retry dialog"; closeConnection(); error(errCode, QString()); return; } // If the user name changes, we have to restablish connection again // since the user name must always be set before calling ssh_connect. if (wasUsernameChanged(username, info)) { qCDebug(KIO_SFTP_LOG) << "Username changed to" << info.username; if (!info.url.userName().isEmpty()) { info.url.setUserName(info.username); } closeConnection(); if (!sftpOpenConnection(info)) { return; } } } rc = ssh_userauth_password(mSession, info.username.toUtf8().constData(), info.password.toUtf8().constData()); if (rc == SSH_AUTH_SUCCESS) { break; } else if (rc == SSH_AUTH_ERROR) { qCDebug(KIO_SFTP_LOG) << "Password authentication failed:" << QString::fromUtf8(ssh_get_error(mSession)); closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } isFirstLoginAttempt = false; // failed attempt to login. info.password.clear(); // clear the password after failed attempts. } } // If we're still not authenticated then we need to leave. if (rc != SSH_AUTH_SUCCESS) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); return; } // start sftp session qCDebug(KIO_SFTP_LOG) << "Trying to request the sftp session"; mSftp = sftp_new(mSession); - if (mSftp == NULL) { + if (mSftp == nullptr) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to request the SFTP subsystem. " "Make sure SFTP is enabled on the server.")); return; } qCDebug(KIO_SFTP_LOG) << "Trying to initialize the sftp session"; if (sftp_init(mSftp) < 0) { closeConnection(); error(KIO::ERR_COULD_NOT_LOGIN, i18n("Could not initialize the SFTP session.")); return; } // Login succeeded! infoMessage(i18n("Successfully connected to %1", mHost)); if (info.keepPassword) { qCDebug(KIO_SFTP_LOG) << "Caching info.username = " << info.username << ", info.url = " << info.url.toDisplayString(); cacheAuthentication(info); } // Update the original username in case it was changed! if (!mUsername.isEmpty()) { mUsername = info.username; } setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); mConnected = true; connected(); info.password.fill('x'); info.password.clear(); } void sftpProtocol::closeConnection() { qCDebug(KIO_SFTP_LOG); if (mSftp) { sftp_free(mSftp); - mSftp = NULL; + mSftp = nullptr; } if (mSession) { ssh_disconnect(mSession); - mSession = NULL; + mSession = nullptr; } mConnected = false; } void sftpProtocol::special(const QByteArray &) { int rc; qCDebug(KIO_SFTP_LOG) << "special(): polling"; if (!mSftp) return; /* * ssh_channel_poll() returns the number of bytes that may be read on the * channel. It does so by checking the input buffer and eventually the * network socket for data to read. If the input buffer is not empty, it * will not probe the network (and such not read packets nor reply to * keepalives). * * As ssh_channel_poll can act on two specific buffers (a channel has two * different stream: stdio and stderr), polling for data on the stderr * stream has more chance of not being in the problematic case (data left * in the buffer). Checking the return value (for >0) would be a good idea * to debug the problem. */ rc = ssh_channel_poll(mSftp->channel, 0); if (rc > 0) { rc = ssh_channel_poll(mSftp->channel, 1); } if (rc < 0) { qCDebug(KIO_SFTP_LOG) << "ssh_channel_poll failed: " << ssh_get_error(mSession); } setTimeoutSpecialCommand(KIO_SFTP_SPECIAL_TIMEOUT); } void sftpProtocol::open(const QUrl &url, QIODevice::OpenMode mode) { qCDebug(KIO_SFTP_LOG) << "open: " << url; if (!sftpLogin()) { return; } const QString path = url.path(); const QByteArray path_c = path.toUtf8(); sftp_attributes sb = sftp_lstat(mSftp, path_c.constData()); - if (sb == NULL) { + if (sb == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } switch (sb->type) { case SSH_FILEXFER_TYPE_DIRECTORY: error(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); sftp_attributes_free(sb); return; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); sftp_attributes_free(sb); return; case SSH_FILEXFER_TYPE_SYMLINK: case SSH_FILEXFER_TYPE_REGULAR: break; } KIO::filesize_t fileSize = sb->size; sftp_attributes_free(sb); int flags = 0; if (mode & QIODevice::ReadOnly) { if (mode & QIODevice::WriteOnly) { flags = O_RDWR | O_CREAT; } else { flags = O_RDONLY; } } else if (mode & QIODevice::WriteOnly) { flags = O_WRONLY | O_CREAT; } if (mode & QIODevice::Append) { flags |= O_APPEND; } else if (mode & QIODevice::Truncate) { flags |= O_TRUNC; } if (flags & O_CREAT) { mOpenFile = sftp_open(mSftp, path_c.constData(), flags, 0644); } else { mOpenFile = sftp_open(mSftp, path_c.constData(), flags, 0); } - if (mOpenFile == NULL) { + if (mOpenFile == nullptr) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, path); return; } // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // If we're not opening the file ReadOnly or ReadWrite, don't attempt to // read the file and send the mimetype. if (mode & QIODevice::ReadOnly) { size_t bytesRequested = 1024; ssize_t bytesRead = 0; QVarLengthArray buffer(bytesRequested); bytesRead = sftp_read(mOpenFile, buffer.data(), bytesRequested); if (bytesRead < 0) { error(KIO::ERR_COULD_NOT_READ, mOpenUrl.toDisplayString()); close(); return; } else { QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(mOpenUrl.fileName(), fileData); emit mimeType(mime.name()); // Go back to the beginning of the file. sftp_rewind(mOpenFile); } } mOpenUrl = url; openOffset = 0; totalSize(fileSize); position(0); opened(); } void sftpProtocol::read(KIO::filesize_t bytes) { qCDebug(KIO_SFTP_LOG) << "read, offset = " << openOffset << ", bytes = " << bytes; - Q_ASSERT(mOpenFile != NULL); + Q_ASSERT(mOpenFile != nullptr); QVarLengthArray buffer(bytes); ssize_t bytesRead = sftp_read(mOpenFile, buffer.data(), bytes); Q_ASSERT(bytesRead <= static_cast(bytes)); if (bytesRead < 0) { qCDebug(KIO_SFTP_LOG) << "Could not read " << mOpenUrl; error(KIO::ERR_COULD_NOT_READ, mOpenUrl.toDisplayString()); close(); return; } const QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); data(fileData); } void sftpProtocol::write(const QByteArray &data) { qCDebug(KIO_SFTP_LOG) << "write, offset = " << openOffset << ", bytes = " << data.size(); - Q_ASSERT(mOpenFile != NULL); + Q_ASSERT(mOpenFile != nullptr); ssize_t bytesWritten = sftp_write(mOpenFile, data.data(), data.size()); if (bytesWritten < 0) { qCDebug(KIO_SFTP_LOG) << "Could not write to " << mOpenUrl; error(KIO::ERR_COULD_NOT_WRITE, mOpenUrl.toDisplayString()); close(); return; } written(bytesWritten); } void sftpProtocol::seek(KIO::filesize_t offset) { qCDebug(KIO_SFTP_LOG) << "seek, offset = " << offset; - Q_ASSERT(mOpenFile != NULL); + Q_ASSERT(mOpenFile != nullptr); if (sftp_seek64(mOpenFile, static_cast(offset)) < 0) { error(KIO::ERR_COULD_NOT_SEEK, mOpenUrl.path()); close(); } position(sftp_tell64(mOpenFile)); } void sftpProtocol::close() { sftp_close(mOpenFile); - mOpenFile = NULL; + mOpenFile = nullptr; finished(); } void sftpProtocol::get(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << url; int errorCode = 0; const sftpProtocol::StatusCode cs = sftpGet(url, errorCode); if (cs == sftpProtocol::Success) { finished(); return; } // The call to sftpGet should only return server side errors since the file // descriptor parameter is set to -1. if (cs == sftpProtocol::ServerError && errorCode) { error(errorCode, url.toDisplayString()); } } sftpProtocol::StatusCode sftpProtocol::sftpGet(const QUrl& url, int& errorCode, KIO::fileoffset_t offset, int fd) { qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { return sftpProtocol::ServerError; } QByteArray path = url.path().toUtf8(); - sftp_file file = NULL; + sftp_file file = nullptr; KIO::filesize_t totalbytesread = 0; QByteArray filedata; sftp_attributes sb = sftp_lstat(mSftp, path.constData()); - if (sb == NULL) { + if (sb == nullptr) { errorCode = toKIOError(sftp_get_error(mSftp)); return sftpProtocol::ServerError; } switch (sb->type) { case SSH_FILEXFER_TYPE_DIRECTORY: errorCode = KIO::ERR_IS_DIRECTORY; sftp_attributes_free(sb); return sftpProtocol::ServerError; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: errorCode = KIO::ERR_CANNOT_OPEN_FOR_READING; sftp_attributes_free(sb); return sftpProtocol::ServerError; case SSH_FILEXFER_TYPE_SYMLINK: case SSH_FILEXFER_TYPE_REGULAR: break; } // Open file file = sftp_open(mSftp, path.constData(), O_RDONLY, 0); - if (file == NULL) { + if (file == nullptr) { errorCode = KIO::ERR_CANNOT_OPEN_FOR_READING; sftp_attributes_free(sb); return sftpProtocol::ServerError; } char mimeTypeBuf[1024]; ssize_t bytesread = sftp_read(file, mimeTypeBuf, sizeof(mimeTypeBuf)); if (bytesread < 0) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } else { QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(url.fileName(), QByteArray(mimeTypeBuf, bytesread)); if (!mime.isDefault()) { emit mimeType(mime.name()); } else { mime = db.mimeTypeForUrl(url); emit mimeType(mime.name()); } sftp_rewind(file); } // Set the total size totalSize(sb->size); // If offset is not specified, check the "resume" meta-data. if (offset < 0) { const QString resumeOffsetStr = metaData(QLatin1String("resume")); if (!resumeOffsetStr.isEmpty()) { bool ok; qlonglong resumeOffset = resumeOffsetStr.toLongLong(&ok); if (ok) { offset = resumeOffset; } } } // If we can resume, offset the buffer properly. if (offset > 0 && ((unsigned long long) offset < sb->size)) { if (sftp_seek64(file, offset) == 0) { canResume(); totalbytesread = offset; qCDebug(KIO_SFTP_LOG) << "Resume offset: " << QString::number(offset); } } bytesread = 0; sftpProtocol::GetRequest request(file, sb); for (;;) { // Enqueue get requests if (!request.enqueueChunks()) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } filedata.clear(); bytesread = request.readChunks(filedata); // Read pending get requests if (bytesread == -1) { errorCode = KIO::ERR_COULD_NOT_READ; return sftpProtocol::ServerError; } else if (bytesread == 0) { if (file->eof) break; else continue; } if (fd == -1) { data(filedata); } else if ((errorCode = writeToFile(fd, filedata.constData(), filedata.size())) != 0) { return sftpProtocol::ClientError; } // increment total bytes read totalbytesread += filedata.length(); processedSize(totalbytesread); } if (fd == -1) data(QByteArray()); processedSize(static_cast(sb->size)); return sftpProtocol::Success; } void sftpProtocol::put(const QUrl& url, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << url << ", permissions =" << permissions << ", overwrite =" << (flags & KIO::Overwrite) << ", resume =" << (flags & KIO::Resume); qCDebug(KIO_SFTP_LOG) << url; int errorCode = 0; const sftpProtocol::StatusCode cs = sftpPut(url, permissions, flags, errorCode); if (cs == sftpProtocol::Success) { finished(); return; } // The call to sftpPut should only return server side errors since the file // descriptor parameter is set to -1. if (cs == sftpProtocol::ServerError && errorCode) { error(errorCode, url.toDisplayString()); } } sftpProtocol::StatusCode sftpProtocol::sftpPut(const QUrl& url, int permissions, JobFlags flags, int& errorCode, int fd) { qCDebug(KIO_SFTP_LOG) << url << ", permissions =" << permissions << ", overwrite =" << (flags & KIO::Overwrite) << ", resume =" << (flags & KIO::Resume); if (!sftpLogin()) { return sftpProtocol::ServerError; } const QString dest_orig = url.path(); const QByteArray dest_orig_c = dest_orig.toUtf8(); const QString dest_part = dest_orig + ".part"; const QByteArray dest_part_c = dest_part.toUtf8(); uid_t owner = 0; gid_t group = 0; sftp_attributes sb = sftp_lstat(mSftp, dest_orig_c.constData()); - const bool bOrigExists = (sb != NULL); + const bool bOrigExists = (sb != nullptr); bool bPartExists = false; const bool bMarkPartial = config()->readEntry("MarkPartial", true); // Don't change permissions of the original file if (bOrigExists) { permissions = sb->permissions; owner = sb->uid; group = sb->gid; } if (bMarkPartial) { sftp_attributes sbPart = sftp_lstat(mSftp, dest_part_c.constData()); - bPartExists = (sbPart != NULL); + bPartExists = (sbPart != nullptr); if (bPartExists && !(flags & KIO::Resume) && !(flags & KIO::Overwrite) && sbPart->size > 0 && sbPart->type == SSH_FILEXFER_TYPE_REGULAR) { if (fd == -1) { // Maybe we can use this partial file for resuming // Tell about the size we have, and the app will tell us // if it's ok to resume or not. qCDebug(KIO_SFTP_LOG) << "calling canResume with " << sbPart->size; flags |= canResume(sbPart->size) ? KIO::Resume : KIO::DefaultFlags; qCDebug(KIO_SFTP_LOG) << "put got answer " << (flags & KIO::Resume); } else { KIO::filesize_t pos = seekPos(fd, sbPart->size, SEEK_SET); if (pos != sbPart->size) { qCDebug(KIO_SFTP_LOG) << "Failed to seek to" << sbPart->size << "bytes in source file. Reason given" << strerror(errno); sftp_attributes_free(sb); sftp_attributes_free(sbPart); errorCode = ERR_COULD_NOT_SEEK; return sftpProtocol::ClientError; } flags |= KIO::Resume; } qCDebug(KIO_SFTP_LOG) << "Resuming at" << sbPart->size; sftp_attributes_free(sbPart); } } if (bOrigExists && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { errorCode = KIO::ERR_DIR_ALREADY_EXIST; } else { errorCode = KIO::ERR_FILE_ALREADY_EXIST; } sftp_attributes_free(sb); return sftpProtocol::ServerError; } QByteArray dest; int result = -1; - sftp_file file = NULL; + sftp_file file = nullptr; StatusCode cs = sftpProtocol::Success; KIO::fileoffset_t totalBytesSent = 0; // Loop until we got 0 (end of data) do { QByteArray buffer; if (fd == -1) { dataReq(); // Request for data result = readData(buffer); } else { char buf[MAX_XFER_BUF_SIZE]; // result = ::read(fd, buf, sizeof(buf)); if(result < 0) { errorCode = ERR_COULD_NOT_READ; cs = sftpProtocol::ClientError; break; } buffer = QByteArray(buf, result); } if (result >= 0) { if (dest.isEmpty()) { if (bMarkPartial) { qCDebug(KIO_SFTP_LOG) << "Appending .part extension to" << dest_orig; dest = dest_part_c; if (bPartExists && !(flags & KIO::Resume)) { qCDebug(KIO_SFTP_LOG) << "Deleting partial file" << dest_part; sftp_unlink(mSftp, dest_part_c.constData()); // Catch errors when we try to open the file. } } else { dest = dest_orig_c; // Will be automatically truncated below... } // bMarkPartial if ((flags & KIO::Resume)) { sftp_attributes fstat; qCDebug(KIO_SFTP_LOG) << "Trying to append: " << dest; file = sftp_open(mSftp, dest.constData(), O_RDWR, 0); // append if resuming if (file) { fstat = sftp_fstat(file); if (fstat) { sftp_seek64(file, fstat->size); // Seek to end TODO totalBytesSent += fstat->size; sftp_attributes_free(fstat); } } } else { mode_t initialMode; if (permissions != -1) { initialMode = permissions | S_IWUSR | S_IRUSR; } else { initialMode = 0644; } qCDebug(KIO_SFTP_LOG) << "Trying to open:" << QString(dest) << ", mode=" << QString::number(initialMode); file = sftp_open(mSftp, dest.constData(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); } // flags & KIO::Resume - if (file == NULL) { + if (file == nullptr) { qCDebug(KIO_SFTP_LOG) << "COULD NOT WRITE " << QString(dest) << ", permissions=" << permissions << ", error=" << ssh_get_error(mSession); if (sftp_get_error(mSftp) == SSH_FX_PERMISSION_DENIED) { errorCode = KIO::ERR_WRITE_ACCESS_DENIED; } else { errorCode = KIO::ERR_CANNOT_OPEN_FOR_WRITING; } cs = sftpProtocol::ServerError; result = -1; continue; } // file } // dest.isEmpty ssize_t bytesWritten = sftp_write(file, buffer.data(), buffer.size()); if (bytesWritten < 0) { errorCode = KIO::ERR_COULD_NOT_WRITE; result = -1; } else { totalBytesSent += bytesWritten; emit processedSize(totalBytesSent); } } // result } while (result > 0); sftp_attributes_free(sb); // An error occurred deal with it. if (result < 0) { qCDebug(KIO_SFTP_LOG) << "Error during 'put'. Aborting."; - if (file != NULL) { + if (file != nullptr) { sftp_close(file); sftp_attributes attr = sftp_stat(mSftp, dest.constData()); - if (bMarkPartial && attr != NULL) { + if (bMarkPartial && attr != nullptr) { size_t size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (attr->size < size) { sftp_unlink(mSftp, dest.constData()); } } sftp_attributes_free(attr); } //::exit(255); return cs; } - if (file == NULL) { // we got nothing to write out, so we never opened the file + if (file == nullptr) { // we got nothing to write out, so we never opened the file return sftpProtocol::Success; } if (sftp_close(file) < 0) { qCWarning(KIO_SFTP_LOG) << "Error when closing file descriptor"; error(KIO::ERR_COULD_NOT_WRITE, dest_orig); return sftpProtocol::ServerError; } // after full download rename the file back to original name if (bMarkPartial) { // If the original URL is a symlink and we were asked to overwrite it, // remove the symlink first. This ensures that we do not overwrite the // current source if the symlink points to it. if ((flags & KIO::Overwrite)) { sftp_unlink(mSftp, dest_orig_c.constData()); } if (sftp_rename(mSftp, dest.constData(), dest_orig_c.constData()) < 0) { qCWarning(KIO_SFTP_LOG) << " Couldn't rename " << dest << " to " << dest_orig; errorCode = KIO::ERR_CANNOT_RENAME_PARTIAL; return sftpProtocol::ServerError; } } // set final permissions if (permissions != -1 && !(flags & KIO::Resume)) { qCDebug(KIO_SFTP_LOG) << "Trying to set final permissions of " << dest_orig << " to " << QString::number(permissions); if (sftp_chmod(mSftp, dest_orig_c.constData(), permissions) < 0) { errorCode = -1; // force copy to call sftpSendWarning... return sftpProtocol::ServerError; } } // set original owner and group if (bOrigExists) { qCDebug(KIO_SFTP_LOG) << "Trying to restore original owner and group of " << dest_orig; if (sftp_chown(mSftp, dest_orig_c.constData(), owner, group) < 0) { qCWarning(KIO_SFTP_LOG) << "Could not change owner and group for" << dest_orig; // warning(i18n( "Could not change owner and group for\n%1", dest_orig)); } } // set modification time const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct timeval times[2]; sftp_attributes attr = sftp_lstat(mSftp, dest_orig_c.constData()); - if (attr != NULL) { + if (attr != nullptr) { times[0].tv_sec = attr->atime; //// access time, unchanged times[1].tv_sec = dt.toTime_t(); // modification time times[0].tv_usec = times[1].tv_usec = 0; qCDebug(KIO_SFTP_LOG) << "Trying to restore mtime for " << dest_orig << " to: " << mtimeStr; result = sftp_utimes(mSftp, dest_orig_c.constData(), times); if (result < 0) { qCWarning(KIO_SFTP_LOG) << "Failed to set mtime for" << dest_orig; } sftp_attributes_free(attr); } } } return sftpProtocol::Success; } void sftpProtocol::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << src << " -> " << dest << " , permissions = " << QString::number(permissions) << ", overwrite = " << (flags & KIO::Overwrite) << ", resume = " << (flags & KIO::Resume); QString sCopyFile; int errorCode = 0; StatusCode cs = sftpProtocol::ClientError; const bool isSourceLocal = src.isLocalFile(); const bool isDestinationLocal = dest.isLocalFile(); if (!isSourceLocal && isDestinationLocal) { // sftp -> file sCopyFile = dest.toLocalFile(); cs = sftpCopyGet(src, sCopyFile, permissions, flags, errorCode); if (cs == sftpProtocol::ServerError) sCopyFile = src.url(); } else if (isSourceLocal && !isDestinationLocal) { // file -> sftp sCopyFile = src.toLocalFile(); cs = sftpCopyPut(dest, sCopyFile, permissions, flags, errorCode); if (cs == sftpProtocol::ServerError) sCopyFile = dest.url(); } else { errorCode = KIO::ERR_UNSUPPORTED_ACTION; sCopyFile.clear(); } // On success or errorcode < 0, emit the finished signal and // send a warning message to the client if errorCode < 0. if (cs == sftpProtocol::Success || errorCode < 0) { if (errorCode < 0) sftpSendWarning(errorCode, sCopyFile); finished(); return; } if (errorCode) { error(errorCode, sCopyFile); } } sftpProtocol::StatusCode sftpProtocol::sftpCopyGet(const QUrl& url, const QString& sCopyFile, int permissions, KIO::JobFlags flags, int& errorCode) { qCDebug(KIO_SFTP_LOG) << url << "->" << sCopyFile << ", permissions=" << permissions; // check if destination is ok ... QT_STATBUF buff; const bool bDestExists = (QT_STAT(QFile::encodeName(sCopyFile), &buff) != -1); if(bDestExists) { if(S_ISDIR(buff.st_mode)) { errorCode = ERR_IS_DIRECTORY; return sftpProtocol::ClientError; } if(!(flags & KIO::Overwrite)) { errorCode = ERR_FILE_ALREADY_EXIST; return sftpProtocol::ClientError; } } bool bResume = false; const QString sPart = sCopyFile + QLatin1String(".part"); // do we have a ".part" file? const bool bPartExists = (QT_STAT(QFile::encodeName(sPart), &buff) != -1); const bool bMarkPartial = config()->readEntry("MarkPartial", true); const QString dest = (bMarkPartial ? sPart : sCopyFile); if (bMarkPartial && bPartExists && buff.st_size > 0) { if (S_ISDIR(buff.st_mode)) { errorCode = ERR_DIR_ALREADY_EXIST; return sftpProtocol::ClientError; // client side error } bResume = canResume( buff.st_size ); } if (bPartExists && !bResume) // get rid of an unwanted ".part" file QFile::remove(sPart); // WABA: Make sure that we keep writing permissions ourselves, // otherwise we can be in for a surprise on NFS. mode_t initialMode; if (permissions != -1) initialMode = permissions | S_IWUSR; else initialMode = 0666; // open the output file ... int fd = -1; KIO::fileoffset_t offset = 0; if (bResume) { fd = QT_OPEN( QFile::encodeName(sPart), O_RDWR ); // append if resuming offset = seekPos(fd, 0, SEEK_END); if(offset < 0) { errorCode = ERR_CANNOT_RESUME; ::close(fd); return sftpProtocol::ClientError; // client side error } qCDebug(KIO_SFTP_LOG) << "resuming at" << offset; } else { fd = QT_OPEN(QFile::encodeName(dest), O_CREAT | O_TRUNC | O_WRONLY, initialMode); } if (fd == -1) { qCDebug(KIO_SFTP_LOG) << "could not write to" << sCopyFile; errorCode = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED : ERR_CANNOT_OPEN_FOR_WRITING; return sftpProtocol::ClientError; } StatusCode result = sftpGet(url, errorCode, offset, fd); if( ::close(fd) && result == sftpProtocol::Success ) { errorCode = ERR_COULD_NOT_WRITE; result = sftpProtocol::ClientError; } // handle renaming or deletion of a partial file ... if (bMarkPartial) { if (result == sftpProtocol::Success) { // rename ".part" on success if ( !QFile::rename( QFile::encodeName(sPart), sCopyFile ) ) { // If rename fails, try removing the destination first if it exists. if (!bDestExists || !QFile::remove(sCopyFile) || !QFile::rename(sPart, sCopyFile)) { qCDebug(KIO_SFTP_LOG) << "cannot rename " << sPart << " to " << sCopyFile; errorCode = ERR_CANNOT_RENAME_PARTIAL; result = sftpProtocol::ClientError; } } } else if (QT_STAT( QFile::encodeName(sPart), &buff ) == 0) { // should a very small ".part" be deleted? const int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (buff.st_size < size) QFile::remove(sPart); } } const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf; utbuf.actime = buff.st_atime; // access time, unchanged utbuf.modtime = dt.toTime_t(); // modification time utime(QFile::encodeName(sCopyFile), &utbuf); } } return result; } sftpProtocol::StatusCode sftpProtocol::sftpCopyPut(const QUrl& url, const QString& sCopyFile, int permissions, JobFlags flags, int& errorCode) { qCDebug(KIO_SFTP_LOG) << sCopyFile << "->" << url << ", permissions=" << permissions << ", flags" << flags; // check if source is ok ... QT_STATBUF buff; bool bSrcExists = (QT_STAT(QFile::encodeName(sCopyFile), &buff) != -1); if (bSrcExists) { if (S_ISDIR(buff.st_mode)) { errorCode = ERR_IS_DIRECTORY; return sftpProtocol::ClientError; } } else { errorCode = ERR_DOES_NOT_EXIST; return sftpProtocol::ClientError; } const int fd = QT_OPEN(QFile::encodeName(sCopyFile), O_RDONLY); if(fd == -1) { errorCode = ERR_CANNOT_OPEN_FOR_READING; return sftpProtocol::ClientError; } totalSize(buff.st_size); // delegate the real work (errorCode gets status) ... StatusCode ret = sftpPut(url, permissions, flags, errorCode, fd); ::close(fd); return ret; } void sftpProtocol::stat(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { return; } if (url.path().isEmpty() || QDir::isRelativePath(url.path()) || url.path().contains("/./") || url.path().contains("/../")) { QString cPath; if (!url.path().isEmpty()) { cPath = canonicalizePath(url.path()); } else { cPath = canonicalizePath(QLatin1String(".")); } if (cPath.isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } QUrl redir(url); redir.setPath(cPath); redirection(redir); qCDebug(KIO_SFTP_LOG) << "redirecting to " << redir.url(); finished(); return; } QByteArray path = url.path().toUtf8(); const QString sDetails = metaData(QLatin1String("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); UDSEntry entry; entry.clear(); if (!createUDSEntry(url.fileName(), path, entry, details)) { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } statEntry(entry); finished(); } void sftpProtocol::mimetype(const QUrl& url){ qCDebug(KIO_SFTP_LOG) << url; if (!sftpLogin()) { return; } // open() feeds the mimetype open(url, QIODevice::ReadOnly); close(); finished(); } void sftpProtocol::listDir(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << "list directory: " << url; if (!sftpLogin()) { return; } if (url.path().isEmpty() || QDir::isRelativePath(url.path()) || url.path().contains("/./") || url.path().contains("/../")) { QString cPath; if (!url.path().isEmpty() ) { cPath = canonicalizePath(url.path()); } else { cPath = canonicalizePath(QStringLiteral(".")); } if (cPath.isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } QUrl redir(url); redir.setPath(cPath); redirection(redir); qCDebug(KIO_SFTP_LOG) << "redirecting to " << redir.url(); finished(); return; } QByteArray path = url.path().toUtf8(); sftp_dir dp = sftp_opendir(mSftp, path.constData()); - if (dp == NULL) { + if (dp == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } - sftp_attributes dirent = NULL; + sftp_attributes dirent = nullptr; const QString sDetails = metaData(QLatin1String("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); UDSEntry entry; qCDebug(KIO_SFTP_LOG) << "readdir: " << path << ", details: " << QString::number(details); for (;;) { mode_t access; mode_t type; char *link; dirent = sftp_readdir(mSftp, dp); - if (dirent == NULL) { + if (dirent == nullptr) { break; } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, QFile::decodeName(dirent->name)); if (dirent->type == SSH_FILEXFER_TYPE_SYMLINK) { QByteArray file = path + '/' + QFile::decodeName(dirent->name).toUtf8(); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); link = sftp_readlink(mSftp, file.constData()); - if (link == NULL) { + if (link == nullptr) { sftp_attributes_free(dirent); error(KIO::ERR_INTERNAL, i18n("Could not read link: %1", QString::fromUtf8(file))); return; } entry.insert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(link)); delete link; // A symlink -> follow it only if details > 1 if (details > 1) { sftp_attributes sb = sftp_stat(mSftp, file.constData()); - if (sb == NULL) { + if (sb == nullptr) { // It is a link pointing to nowhere type = S_IFMT - 1; access = S_IRWXU | S_IRWXG | S_IRWXO; entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type); entry.insert( KIO::UDSEntry::UDS_ACCESS, access); entry.insert( KIO::UDSEntry::UDS_SIZE, 0LL ); goto notype; } sftp_attributes_free(dirent); dirent = sb; } } switch (dirent->type) { case SSH_FILEXFER_TYPE_REGULAR: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); break; case SSH_FILEXFER_TYPE_DIRECTORY: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case SSH_FILEXFER_TYPE_SYMLINK: entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFLNK); break; case SSH_FILEXFER_TYPE_SPECIAL: case SSH_FILEXFER_TYPE_UNKNOWN: break; } access = dirent->permissions & 07777; entry.insert(KIO::UDSEntry::UDS_ACCESS, access); entry.insert(KIO::UDSEntry::UDS_SIZE, dirent->size); notype: if (details > 0) { if (dirent->owner) { entry.insert(KIO::UDSEntry::UDS_USER, QString::fromUtf8(dirent->owner)); } else { entry.insert(KIO::UDSEntry::UDS_USER, QString::number(dirent->uid)); } if (dirent->group) { entry.insert(KIO::UDSEntry::UDS_GROUP, QString::fromUtf8(dirent->group)); } else { entry.insert(KIO::UDSEntry::UDS_GROUP, QString::number(dirent->gid)); } entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, dirent->atime); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dirent->mtime); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, dirent->createtime); } sftp_attributes_free(dirent); listEntry(entry); } // for ever sftp_closedir(dp); finished(); } void sftpProtocol::mkdir(const QUrl &url, int permissions) { qCDebug(KIO_SFTP_LOG) << "create directory: " << url; if (!sftpLogin()) { return; } if (url.path().isEmpty()) { error(KIO::ERR_MALFORMED_URL, url.toDisplayString()); return; } const QString path = url.path(); const QByteArray path_c = path.toUtf8(); // Remove existing file or symlink, if requested. if (metaData(QLatin1String("overwrite")) == QLatin1String("true")) { qCDebug(KIO_SFTP_LOG) << "overwrite set, remove existing file or symlink: " << url; sftp_unlink(mSftp, path_c.constData()); } qCDebug(KIO_SFTP_LOG) << "Trying to create directory: " << path; sftp_attributes sb = sftp_lstat(mSftp, path_c.constData()); - if (sb == NULL) { + if (sb == nullptr) { if (sftp_mkdir(mSftp, path_c.constData(), 0777) < 0) { reportError(url, sftp_get_error(mSftp)); sftp_attributes_free(sb); return; } else { qCDebug(KIO_SFTP_LOG) << "Successfully created directory: " << url; if (permissions != -1) { chmod(url, permissions); } else { finished(); } sftp_attributes_free(sb); return; } } if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { error(KIO::ERR_DIR_ALREADY_EXIST, path); } else { error(KIO::ERR_FILE_ALREADY_EXIST, path); } sftp_attributes_free(sb); return; } void sftpProtocol::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << "rename " << src << " to " << dest << flags; if (!sftpLogin()) { return; } QByteArray qsrc = src.path().toUtf8(); QByteArray qdest = dest.path().toUtf8(); sftp_attributes sb = sftp_lstat(mSftp, qdest.constData()); - if (sb != NULL) { + if (sb != nullptr) { if (!(flags & KIO::Overwrite)) { if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { error(KIO::ERR_DIR_ALREADY_EXIST, dest.url()); } else { error(KIO::ERR_FILE_ALREADY_EXIST, dest.url()); } sftp_attributes_free(sb); return; } // Delete the existing destination file/dir... if (sb->type == SSH_FILEXFER_TYPE_DIRECTORY) { if (sftp_rmdir(mSftp, qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } } else { if (sftp_unlink(mSftp, qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } } } sftp_attributes_free(sb); if (sftp_rename(mSftp, qsrc.constData(), qdest.constData()) < 0) { reportError(dest, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) { qCDebug(KIO_SFTP_LOG) << "link " << target << "->" << dest << ", overwrite = " << (flags & KIO::Overwrite) << ", resume = " << (flags & KIO::Resume); if (!sftpLogin()) { return; } QByteArray t = target.toUtf8(); QByteArray d = dest.path().toUtf8(); bool failed = false; if (sftp_symlink(mSftp, t.constData(), d.constData()) < 0) { if (flags == KIO::Overwrite) { sftp_attributes sb = sftp_lstat(mSftp, d.constData()); - if (sb == NULL) { + if (sb == nullptr) { failed = true; } else { if (sftp_unlink(mSftp, d.constData()) < 0) { failed = true; } else { if (sftp_symlink(mSftp, t.constData(), d.constData()) < 0) { failed = true; } } } sftp_attributes_free(sb); } } if (failed) { reportError(dest, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::chmod(const QUrl& url, int permissions) { qCDebug(KIO_SFTP_LOG) << "change permission of " << url << " to " << QString::number(permissions); if (!sftpLogin()) { return; } QByteArray path = url.path().toUtf8(); if (sftp_chmod(mSftp, path.constData(), permissions) < 0) { reportError(url, sftp_get_error(mSftp)); return; } finished(); } void sftpProtocol::del(const QUrl &url, bool isfile){ qCDebug(KIO_SFTP_LOG) << "deleting " << (isfile ? "file: " : "directory: ") << url; if (!sftpLogin()) { return; } QByteArray path = url.path().toUtf8(); if (isfile) { if (sftp_unlink(mSftp, path.constData()) < 0) { reportError(url, sftp_get_error(mSftp)); return; } } else { if (sftp_rmdir(mSftp, path.constData()) < 0) { reportError(url, sftp_get_error(mSftp)); return; } } finished(); } void sftpProtocol::slave_status() { qCDebug(KIO_SFTP_LOG) << "connected to " << mHost << "?: " << mConnected; slaveStatus((mConnected ? mHost : QString()), mConnected); } sftpProtocol::GetRequest::GetRequest(sftp_file file, sftp_attributes sb, ushort maxPendingRequests) :mFile(file), mSb(sb), mMaxPendingRequests(maxPendingRequests) { } bool sftpProtocol::GetRequest::enqueueChunks() { sftpProtocol::GetRequest::Request request; qCDebug(KIO_SFTP_LOG) << "enqueueChunks"; while (pendingRequests.count() < mMaxPendingRequests) { request.expectedLength = MAX_XFER_BUF_SIZE; request.startOffset = mFile->offset; request.id = sftp_async_read_begin(mFile, request.expectedLength); if (request.id < 0) { if (pendingRequests.isEmpty()) { return false; } else { break; } } pendingRequests.enqueue(request); if (mFile->offset >= mSb->size) { // Do not add any more chunks if the offset is larger than the given file size. // However this is done after adding a request as the remote file size may // have changed in the meantime. break; } } qCDebug(KIO_SFTP_LOG) << "enqueueChunks done" << QString::number(pendingRequests.size()); return true; } int sftpProtocol::GetRequest::readChunks(QByteArray &data) { int totalRead = 0; ssize_t bytesread = 0; while (!pendingRequests.isEmpty()) { sftpProtocol::GetRequest::Request &request = pendingRequests.head(); int dataSize = data.size() + request.expectedLength; data.resize(dataSize); if (data.size() < dataSize) { // Could not allocate enough memory - skip current chunk data.resize(dataSize - request.expectedLength); break; } bytesread = sftp_async_read(mFile, data.data() + totalRead, request.expectedLength, request.id); // qCDebug(KIO_SFTP_LOG) << "bytesread=" << QString::number(bytesread); if (bytesread == 0 || bytesread == SSH_AGAIN) { // Done reading or timeout data.resize(data.size() - request.expectedLength); if (bytesread == 0) { pendingRequests.dequeue(); // This frees QByteArray &data! } break; } else if (bytesread == SSH_ERROR) { return -1; } totalRead += bytesread; if (bytesread < request.expectedLength) { int rc; // If less data is read than expected - requeue the request data.resize(data.size() - (request.expectedLength - bytesread)); // Modify current request request.expectedLength -= bytesread; request.startOffset += bytesread; rc = sftp_seek64(mFile, request.startOffset); if (rc < 0) { // Failed to continue reading return -1; } request.id = sftp_async_read_begin(mFile, request.expectedLength); if (request.id < 0) { // Failed to dispatch rerequest return -1; } return totalRead; } pendingRequests.dequeue(); } return totalRead; } sftpProtocol::GetRequest::~GetRequest() { sftpProtocol::GetRequest::Request request; char buf[MAX_XFER_BUF_SIZE]; // Remove pending reads to avoid memory leaks while (!pendingRequests.isEmpty()) { request = pendingRequests.dequeue(); sftp_async_read(mFile, buf, request.expectedLength, request.id); } // Close channel & free attributes sftp_close(mFile); sftp_attributes_free(mSb); } void sftpProtocol::requiresUserNameRedirection() { QUrl redirectUrl; redirectUrl.setScheme( QLatin1String("sftp") ); redirectUrl.setUserName( mUsername ); redirectUrl.setPassword( mPassword ); redirectUrl.setHost( mHost ); if (mPort > 0 && mPort != DEFAULT_SFTP_PORT) { redirectUrl.setPort( mPort ); } qCDebug(KIO_SFTP_LOG) << "redirecting to" << redirectUrl; redirection( redirectUrl ); } bool sftpProtocol::sftpLogin() { const QString origUsername = mUsername; openConnection(); qCDebug(KIO_SFTP_LOG) << "connected ?" << mConnected << "username: old=" << origUsername << "new=" << mUsername; if (!origUsername.isEmpty() && origUsername != mUsername) { requiresUserNameRedirection(); finished(); return false; } return mConnected; } void sftpProtocol::sftpSendWarning(int errorCode, const QString& url) { switch (errorCode) { case -1: warning(i18n( "Could not change permissions for\n%1", url)); break; default: break; } } void sftpProtocol::clearPubKeyAuthInfo() { if (mPublicKeyAuthInfo) { delete mPublicKeyAuthInfo; - mPublicKeyAuthInfo = 0; + mPublicKeyAuthInfo = nullptr; } } void sftpProtocol::fileSystemFreeSpace(const QUrl& url) { qCDebug(KIO_SFTP_LOG) << "file system free space of" << url; if (!sftpLogin()) { return; } if (sftp_extension_supported(mSftp, "statvfs@openssh.com", "2") == 0) { error(ERR_UNSUPPORTED_ACTION, QString()); return; } const QByteArray path = url.path().toUtf8(); sftp_statvfs_t statvfs = sftp_statvfs(mSftp, path.constData()); if (statvfs == nullptr) { reportError(url, sftp_get_error(mSftp)); return; } setMetaData(QString::fromLatin1("total"), QString::number(statvfs->f_frsize * statvfs->f_blocks)); setMetaData(QString::fromLatin1("available"), QString::number(statvfs->f_frsize * statvfs->f_bavail)); sftp_statvfs_free(statvfs); finished(); } diff --git a/thumbnail/comiccreator.cpp b/thumbnail/comiccreator.cpp index af25d7d7..aa6397b1 100644 --- a/thumbnail/comiccreator.cpp +++ b/thumbnail/comiccreator.cpp @@ -1,314 +1,314 @@ /** * This file is part of the KDE libraries * * Comic Book Thumbnailer for KDE 4 v0.1 * Creates cover page previews for comic-book files (.cbr/z/t). * Copyright (c) 2009 Harsh J * * Some code borrowed from Okular's comicbook generators, * by Tobias Koenig * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ // comiccreator.cpp #include "comiccreator.h" #include #include #include #include #include #include #include #include #include #include #include // For KIO-Thumbnail debug outputs // TODO KF5 qCDebug #define KIO_THUMB 11371 extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new ComicCreator; } } -ComicCreator::ComicCreator() : m_loop(0) {} +ComicCreator::ComicCreator() : m_loop(nullptr) {} bool ComicCreator::create(const QString& path, int width, int height, QImage& img) { Q_UNUSED(width); Q_UNUSED(height); QImage cover; // Detect mime type. QMimeDatabase db; db.mimeTypeForFile(path, QMimeDatabase::MatchContent); const QMimeType mime = db.mimeTypeForFile(path, QMimeDatabase::MatchContent); if (mime.inherits("application/x-cbz") || mime.inherits("application/zip")) { // ZIP archive. cover = extractArchiveImage(path, ZIP); } else if (mime.inherits("application/x-cbt") || mime.inherits("application/x-gzip") || mime.inherits("application/x-tar")) { // TAR archive cover = extractArchiveImage(path, TAR); } else if (mime.inherits("application/x-cbr") || mime.inherits("application/x-rar")) { // RAR archive. cover = extractRARImage(path); } if (cover.isNull()) { qDebug()<<"Error creating the comic book thumbnail."; return false; } // Copy the extracted cover to KIO::ThumbCreator's img reference. img = cover; return true; } void ComicCreator::filterImages(QStringList& entries) { /// Sort case-insensitive, then remove non-image entries. QMap entryMap; Q_FOREACH(const QString& entry, entries) { if (entry.endsWith(QLatin1String(".gif"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".png"), Qt::CaseInsensitive)) { entryMap.insert(entry.toLower(), entry); } } entries = entryMap.values(); } QImage ComicCreator::extractArchiveImage(const QString& path, const ComicCreator::Type type) { /// Extracts the cover image out of the .cbz or .cbt file. QScopedPointer cArchive; if (type==ZIP) { // Open the ZIP archive. cArchive.reset(new KZip(path)); } else if (type==TAR) { // Open the TAR archive. cArchive.reset(new KTar(path)); } else { // Reject all other types for this method. return QImage(); } // Can our archive be opened? if (!cArchive->open(QIODevice::ReadOnly)) { return QImage(); } // Get the archive's directory. - const KArchiveDirectory* cArchiveDir = 0; + const KArchiveDirectory* cArchiveDir = nullptr; cArchiveDir = cArchive->directory(); if (!cArchiveDir) { return QImage(); } QStringList entries; // Get and filter the entries from the archive. getArchiveFileList(entries, QString(), cArchiveDir); filterImages(entries); if (entries.isEmpty()) { return QImage(); } // Extract the cover file. const KArchiveFile *coverFile = static_cast (cArchiveDir->entry(entries[0])); if (!coverFile) { return QImage(); } return QImage::fromData(coverFile->data()); } void ComicCreator::getArchiveFileList(QStringList& entries, const QString& prefix, const KArchiveDirectory *dir) { /// Recursively list all files in the ZIP archive into 'entries'. Q_FOREACH (const QString& entry, dir->entries()) { const KArchiveEntry *e = dir->entry(entry); if (e->isDirectory()) { getArchiveFileList(entries, prefix + entry + '/', static_cast(e)); } else if (e->isFile()) { entries.append(prefix + entry); } } } QImage ComicCreator::extractRARImage(const QString& path) { /// Extracts the cover image out of the .cbr file. // Check if unrar is available. Get its path in 'unrarPath'. QString unrar = unrarPath(); if (unrar.isEmpty()) { qDebug()<<"A suitable version of unrar is not available."; return QImage(); } // Get the files and filter the images out. QStringList entries = getRARFileList(path, unrar); filterImages(entries); if (entries.isEmpty()) { return QImage(); } // Clear previously used data arrays. m_stdOut.clear(); m_stdErr.clear(); // Extract the cover file alone. Use verbose paths. // unrar x -n path/to/archive /path/to/temp QTemporaryDir cUnrarTempDir; startProcess(unrar, QStringList() << "x" << "-n" + entries[0] << path << cUnrarTempDir.path()); // Load cover file data into image. QImage cover; cover.load(cUnrarTempDir.path() + QDir::separator() + entries[0]); return cover; } QStringList ComicCreator::getRARFileList(const QString& path, const QString& unrarPath) { /// Get a verbose unrar listing so we can extract a single file later. // CMD: unrar vb /path/to/archive QStringList entries; startProcess(unrarPath, QStringList() << "vb" << path); entries = QString::fromLocal8Bit(m_stdOut).split('\n', QString::SkipEmptyParts); return entries; } QString ComicCreator::unrarPath() const { /// Check the standard paths to see if a suitable unrar is available. QString unrar = QStandardPaths::findExecutable("unrar"); if (unrar.isEmpty()) { unrar = QStandardPaths::findExecutable("unrar-nonfree"); } if (unrar.isEmpty()) { unrar = QStandardPaths::findExecutable("rar"); } if (!unrar.isEmpty()) { QProcess proc; proc.start(unrar, QStringList() << "-version"); proc.waitForFinished(-1); const QStringList lines = QString::fromLocal8Bit(proc.readAllStandardOutput()).split ('\n', QString::SkipEmptyParts); if (!lines.isEmpty()) { if (lines.first().startsWith("RAR ") || lines.first().startsWith("UNRAR ")) { return unrar; } } } return QString(); } void ComicCreator::readProcessOut() { /// Read all std::out data and store to the data array. if (!m_process) return; m_stdOut += m_process->readAllStandardOutput(); } void ComicCreator::readProcessErr() { /// Read available std:err data and kill process if there is any. if (!m_process) return; m_stdErr += m_process->readAllStandardError(); if (!m_stdErr.isEmpty()) { m_process->kill(); return; } } void ComicCreator::finishedProcess(int exitCode, QProcess::ExitStatus exitStatus) { /// Run when process finishes. Q_UNUSED(exitCode) if (m_loop) { m_loop->exit(exitStatus == QProcess::CrashExit ? 1 : 0); } } int ComicCreator::startProcess(const QString& processPath, const QStringList& args) { /// Run a process and store std::out, std::err data in their respective buffers. int ret = 0; #if defined(Q_OS_WIN) m_process.reset(new QProcess(this)); #else m_process.reset(new KPtyProcess(this)); m_process->setOutputChannelMode(KProcess::SeparateChannels); #endif connect(m_process.data(), SIGNAL(readyReadStandardOutput()), SLOT(readProcessOut())); connect(m_process.data(), SIGNAL(readyReadStandardError()), SLOT(readProcessErr())); connect(m_process.data(), SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finishedProcess(int, QProcess::ExitStatus))); #if defined(Q_OS_WIN) m_process->start(processPath, args, QIODevice::ReadWrite | QIODevice::Unbuffered); ret = m_process->waitForFinished(-1) ? 0 : 1; #else m_process->setProgram(processPath, args); m_process->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered); m_process->start(); QEventLoop loop; m_loop = &loop; ret = loop.exec(QEventLoop::WaitForMoreEvents); - m_loop = 0; + m_loop = nullptr; #endif return ret; } ThumbCreator::Flags ComicCreator::flags() const { return None; } diff --git a/thumbnail/djvucreator.cpp b/thumbnail/djvucreator.cpp index ce53133c..b07b96d1 100644 --- a/thumbnail/djvucreator.cpp +++ b/thumbnail/djvucreator.cpp @@ -1,133 +1,133 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Malte Starostik Copyright (C) 2001 Leon Bottou 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 "djvucreator.h" #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new DjVuCreator; } } bool DjVuCreator::create(const QString &path, int width, int height, QImage &img) { int output[2]; QByteArray data(1024, 'k'); bool ok = false; if (pipe(output) == -1) return false; const char* argv[8]; QByteArray sizearg, fnamearg; sizearg = QByteArray::number(width) + 'x' + QByteArray::number(height); fnamearg = QFile::encodeName( path ); argv[0] = "ddjvu"; argv[1] = "-page"; argv[2] = "1"; // krazy:exclude=doublequote_chars argv[3] = "-size"; argv[4] = sizearg.data(); argv[5] = fnamearg.data(); - argv[6] = 0; + argv[6] = nullptr; pid_t pid = fork(); if (pid == 0) { close(output[0]); dup2(output[1], STDOUT_FILENO); execvp(argv[0], const_cast(argv)); exit(1); } else if (pid >= 0) { close(output[1]); int offset = 0; while (!ok) { fd_set fds; FD_ZERO(&fds); FD_SET(output[0], &fds); struct timeval tv; tv.tv_sec = 20; tv.tv_usec = 0; - if (select(output[0] + 1, &fds, 0, 0, &tv) <= 0) { + if (select(output[0] + 1, &fds, nullptr, nullptr, &tv) <= 0) { if (errno == EINTR || errno == EAGAIN) continue; break; // error or timeout } if (FD_ISSET(output[0], &fds)) { int count = read(output[0], data.data() + offset, 1024); if (count == -1) break; if (count) // prepare for next block { offset += count; data.resize(offset + 1024); } else // got all data { data.resize(offset); ok = true; } } } if (!ok) kill(pid, SIGTERM); int status = 0; if (waitpid(pid, &status, 0) != pid || (status != 0 && status != 256) ) ok = false; } else { close(output[1]); } close(output[0]); int l = img.loadFromData( data ); return ok && l; } ThumbCreator::Flags DjVuCreator::flags() const { return static_cast(None); } diff --git a/thumbnail/htmlcreator.cpp b/thumbnail/htmlcreator.cpp index 0083f9c5..59dd7ab5 100644 --- a/thumbnail/htmlcreator.cpp +++ b/thumbnail/htmlcreator.cpp @@ -1,109 +1,109 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Malte Starostik Copyright (C) 2006 Roberto Cappuccio Copyright (C) 2011 Dawit Alemayehu 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 "htmlcreator.h" #include #include #include #include #include extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new HTMLCreator; } } HTMLCreator::HTMLCreator() - : m_loadedOk(true), m_page(0) + : m_loadedOk(true), m_page(nullptr) { } HTMLCreator::~HTMLCreator() { delete m_page; } bool HTMLCreator::create(const QString &path, int width, int height, QImage &img) { if (!m_page) { m_page = new QWebEnginePage; connect(m_page, &QWebEnginePage::loadFinished, this, &HTMLCreator::slotFinished); m_page->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); m_page->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false); m_page->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, false); m_page->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true); } QUrl url = QUrl::fromUserInput(path); // the argument should be a QUrl! m_loadedOk = false; m_page->load(url); const int t = startTimer((url.isLocalFile()?5000:30000)); m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); killTimer(t); if (m_page->contentsSize().isEmpty()) { m_loadedOk = false; } if (!m_loadedOk) { return false; } QPixmap pix; if (width > 400 || height > 600) { if (height * 3 > width * 4) { pix = QPixmap(width, width * 4 / 3); } else { pix = QPixmap(height * 3 / 4, height); } } else { pix = QPixmap(400, 600); } pix.fill(Qt::transparent); m_page->view()->render(&pix); img = pix.toImage(); return true; } void HTMLCreator::timerEvent(QTimerEvent *) { m_eventLoop.quit(); } void HTMLCreator::slotFinished(bool ok) { m_loadedOk = ok; m_eventLoop.quit(); } ThumbCreator::Flags HTMLCreator::flags() const { return None; } #include "htmlcreator.moc" diff --git a/thumbnail/textcreator.cpp b/thumbnail/textcreator.cpp index c6fd7382..449a3b5a 100644 --- a/thumbnail/textcreator.cpp +++ b/thumbnail/textcreator.cpp @@ -1,168 +1,168 @@ /* This file is part of the KDE libraries Copyright (C) 2000,2002 Carsten Pfeiffer 2000 Malte Starostik 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 "textcreator.h" #include #include #include #include #include #include #include // TODO Fix or remove kencodingprober code // #include extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new TextCreator; } } TextCreator::TextCreator() - : m_data(0), + : m_data(nullptr), m_dataSize(0) { } TextCreator::~TextCreator() { delete [] m_data; } static QTextCodec *codecFromContent(const char *data, int dataSize) { #if 0 // ### Use this when KEncodingProber does not return junk encoding for UTF-8 data) KEncodingProber prober; prober.feed(data, dataSize); return QTextCodec::codecForName(prober.encoding()); #else QByteArray ba = QByteArray::fromRawData(data, dataSize); // try to detect UTF text, fall back to locale default (which is usually UTF-8) return QTextCodec::codecForUtfText(ba, QTextCodec::codecForLocale()); #endif } bool TextCreator::create(const QString &path, int width, int height, QImage &img) { bool ok = false; // determine some sizes... // example: width: 60, height: 64 QSize pixmapSize( width, height ); if (height * 3 > width * 4) pixmapSize.setHeight( width * 4 / 3 ); else pixmapSize.setWidth( height * 3 / 4 ); if ( pixmapSize != m_pixmap.size() ) m_pixmap = QPixmap( pixmapSize ); // one pixel for the rectangle, the rest. whitespace int xborder = 1 + pixmapSize.width()/16; // minimum x-border int yborder = 1 + pixmapSize.height()/16; // minimum y-border // this font is supposed to look good at small sizes QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); font.setPixelSize( qMax(7, qMin( 10, ( pixmapSize.height() - 2 * yborder ) / 16 ) ) ); QFontMetrics fm( font ); // calculate a better border so that the text is centered int canvasWidth = pixmapSize.width() - 2*xborder; int canvasHeight = pixmapSize.height() - 2*yborder; int numLines = (int) ( canvasHeight / fm.height() ); // assumes an average line length of <= 120 chars const int bytesToRead = 120 * numLines; // create text-preview QFile file( path ); if ( file.open( QIODevice::ReadOnly )) { if ( !m_data || m_dataSize < bytesToRead + 1 ) { delete [] m_data; m_data = new char[bytesToRead+1]; m_dataSize = bytesToRead + 1; } int read = file.read( m_data, bytesToRead ); if ( read > 0 ) { ok = true; m_data[read] = '\0'; QString text = codecFromContent( m_data, read )->toUnicode( m_data, read ).trimmed(); // FIXME: maybe strip whitespace and read more? // If the text contains tabs or consecutive spaces, it is probably // formatted using white space. Use a fixed pitch font in this case. QStringList textLines = text.split( '\n' ); foreach ( const QString &line, textLines ) { QString trimmedLine = line.trimmed(); if ( trimmedLine.contains( '\t' ) || trimmedLine.contains( " " ) ) { font.setFamily( QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); break; } } #if 0 QPalette palette; QColor bgColor = palette.color( QPalette::Base ); QColor fgColor = palette.color( QPalette::Text ); if ( qGray( bgColor.rgb() ) > qGray( fgColor.rgb() ) ) { bgColor = bgColor.darker( 103 ); } else { bgColor = bgColor.lighter( 103 ); } #else QColor bgColor = QColor ( 245, 245, 245 ); // light-grey background QColor fgColor = Qt::black; #endif m_pixmap.fill( bgColor ); QRect rect; QPainter painter( &m_pixmap ); painter.setFont( font ); painter.setPen( fgColor ); QTextOption textOption( Qt::AlignTop | Qt::AlignLeft ); textOption.setTabStop( 8 * painter.fontMetrics().width( ' ' ) ); textOption.setWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ); painter.drawText( QRect( xborder, yborder, canvasWidth, canvasHeight ), text, textOption ); painter.end(); img = m_pixmap.toImage(); } file.close(); } return ok; } ThumbCreator::Flags TextCreator::flags() const { return (Flags)BlendIcon; } diff --git a/thumbnail/thumbnail.cpp b/thumbnail/thumbnail.cpp index fceb7f8c..502b2d17 100644 --- a/thumbnail/thumbnail.cpp +++ b/thumbnail/thumbnail.cpp @@ -1,765 +1,765 @@ /* 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 // 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. // 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 // 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; 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"); //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_COULD_NOT_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(), 0, 0); + 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()); memcpy(shmaddr, img.bits(), img.byteCount()); 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"); foreach(KService::Ptr plugin, plugins) { const QStringList mimeTypes = plugin->serviceTypes(); foreach(QString mime, mimeTypes) { if(mime.endsWith('*')) { mime = mime.left(mime.length()-1); if(mimeType.startsWith(mime)) 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", qulonglong(5 * 1024 * 1024)); // 5 MByte default } 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 QPixmap folder = QIcon::fromTheme(item.iconName()).pixmap(qMin(m_width, m_height)); 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; // Setup image object for preview with only one tile 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; 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 (validThumbnails == 0) { drawSubThumbnail(oneTilePainter, dir.filePath(), oneTileWidth, oneTileHeight, xPos, yPos, frameWidth); } 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 use image with only one tile if (validThumbnails == 1) { 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 0; + 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) { if (m_enabledPlugins.isEmpty()) { const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); } 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; }