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

0001 /*  This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kio_archivebase.h"
0008 #include <kio_archive_debug.h>
0009 
0010 #include <errno.h>
0011 #include <stdlib.h>
0012 #include <sys/stat.h>
0013 
0014 #include <K7Zip>
0015 #include <KAr>
0016 #include <KIO/StatJob>
0017 #include <KLocalizedString>
0018 #include <KTar>
0019 #include <KUser>
0020 #include <KZip>
0021 #include <kio_version.h>
0022 
0023 #include <QDir>
0024 #include <QFile>
0025 #include <QMimeDatabase>
0026 #include <QMimeType>
0027 #include <QUrl>
0028 
0029 #include <memory>
0030 
0031 #ifdef Q_OS_WIN
0032 #define S_ISDIR(m) (((m & S_IFMT) == S_IFDIR))
0033 #endif
0034 
0035 using namespace KIO;
0036 
0037 ArchiveProtocolBase::ArchiveProtocolBase(const QByteArray &proto, const QByteArray &pool, const QByteArray &app)
0038     : WorkerBase(proto, pool, app)
0039 {
0040     qCDebug(KIO_ARCHIVE_LOG);
0041     m_archiveFile = nullptr;
0042 }
0043 
0044 ArchiveProtocolBase::~ArchiveProtocolBase()
0045 {
0046     delete m_archiveFile;
0047 }
0048 
0049 bool ArchiveProtocolBase::checkNewFile(const QUrl &url, QString &path, KIO::Error &errorNum)
0050 {
0051 #ifndef Q_OS_WIN
0052     QString fullPath = url.path();
0053 #else
0054     QString fullPath = url.path().remove(0, 1);
0055 #endif
0056     qCDebug(KIO_ARCHIVE_LOG) << fullPath;
0057 
0058     // Are we already looking at that file ?
0059     if (m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length())) {
0060         // Has it changed ?
0061         QT_STATBUF statbuf;
0062         if (QT_STAT(QFile::encodeName(m_archiveName).constData(), &statbuf) == 0) {
0063             if (m_mtime == statbuf.st_mtime) {
0064                 path = fullPath.mid(m_archiveName.length());
0065                 qCDebug(KIO_ARCHIVE_LOG) << "returning" << path;
0066                 return true;
0067             }
0068         }
0069     }
0070     qCDebug(KIO_ARCHIVE_LOG) << "Need to open a new file";
0071 
0072     // Close previous file
0073     if (m_archiveFile) {
0074         m_archiveFile->close();
0075         delete m_archiveFile;
0076         m_archiveFile = nullptr;
0077     }
0078 
0079     // Find where the tar file is in the full path
0080     int pos = 0;
0081     QString archiveFile;
0082     path.clear();
0083 
0084     if (!fullPath.isEmpty() && !fullPath.endsWith(QLatin1Char('/')))
0085         fullPath += QLatin1Char('/');
0086 
0087     qCDebug(KIO_ARCHIVE_LOG) << "the full path is" << fullPath;
0088     QT_STATBUF statbuf;
0089     statbuf.st_mode = 0; // be sure to clear the directory bit
0090     while ((pos = fullPath.indexOf(QLatin1Char('/'), pos + 1)) != -1) {
0091         QString tryPath = fullPath.left(pos);
0092         qCDebug(KIO_ARCHIVE_LOG) << fullPath << "trying" << tryPath;
0093         if (QT_STAT(QFile::encodeName(tryPath).constData(), &statbuf) == -1) {
0094             if (errno == ENOENT) {
0095                 // The current path is no longer part of the local filesystem.
0096                 // Either we already have enough of the pathname, or we will
0097                 // not get anything more useful.
0098                 statbuf.st_mode = 0; // do not trust the result
0099                 break;
0100             }
0101 
0102             if (errno == EACCES)
0103                 errorNum = KIO::ERR_ACCESS_DENIED;
0104             else
0105                 errorNum = KIO::ERR_CANNOT_STAT;
0106             return false;
0107         }
0108 
0109         if (!S_ISDIR(statbuf.st_mode)) {
0110             archiveFile = tryPath;
0111             m_mtime = statbuf.st_mtime;
0112 #ifdef Q_OS_WIN // st_uid and st_gid provides no information
0113             m_user.clear();
0114             m_group.clear();
0115 #else
0116             KUser user(statbuf.st_uid);
0117             m_user = user.loginName();
0118             KUserGroup group(statbuf.st_gid);
0119             m_group = group.name();
0120 #endif
0121             path = fullPath.mid(pos + 1);
0122             qCDebug(KIO_ARCHIVE_LOG).nospace() << "fullPath=" << fullPath << " path=" << path;
0123             if (path.length() > 1) {
0124                 if (path.endsWith(QLatin1Char('/')))
0125                     path.chop(1);
0126             } else
0127                 path = QStringLiteral("/");
0128             qCDebug(KIO_ARCHIVE_LOG).nospace() << "Found. archiveFile=" << archiveFile << " path=" << path;
0129             break;
0130         }
0131     }
0132     if (archiveFile.isEmpty()) {
0133         qCDebug(KIO_ARCHIVE_LOG) << "not found";
0134         if (S_ISDIR(statbuf.st_mode)) // Did the last stat() find a directory?
0135         {
0136             // Too bad, it is a directory, not an archive.
0137             qCDebug(KIO_ARCHIVE_LOG) << "Path is a directory, not an archive.";
0138             errorNum = KIO::ERR_IS_DIRECTORY;
0139         } else
0140             errorNum = KIO::ERR_DOES_NOT_EXIST;
0141         return false;
0142     }
0143 
0144     // Open new file
0145     m_archiveFile = this->createArchive(url.scheme(), archiveFile);
0146     if (!m_archiveFile) {
0147         qCWarning(KIO_ARCHIVE_LOG) << "Protocol" << url.scheme() << "not supported by this IOWorker";
0148         errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL;
0149         return false;
0150     }
0151 
0152     if (!m_archiveFile->open(QIODevice::ReadOnly)) {
0153         qCDebug(KIO_ARCHIVE_LOG) << "Opening" << archiveFile << "failed.";
0154         delete m_archiveFile;
0155         m_archiveFile = nullptr;
0156         errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING;
0157         return false;
0158     }
0159 
0160     m_archiveName = archiveFile;
0161     return true;
0162 }
0163 
0164 uint ArchiveProtocolBase::computeArchiveDirSize(const KArchiveDirectory *dir)
0165 {
0166     // compute size of archive content
0167     uint totalSize = 0;
0168     const auto entries = dir->entries();
0169     for (const auto &entryName : entries) {
0170         auto entry = dir->entry(entryName);
0171         if (entry->isFile()) {
0172             auto fileEntry = static_cast<const KArchiveFile *>(entry);
0173             totalSize += fileEntry->size();
0174         } else if (entry->isDirectory()) {
0175             const auto dirEntry = static_cast<const KArchiveDirectory *>(entry);
0176             // recurse
0177             totalSize += computeArchiveDirSize(dirEntry);
0178         }
0179     }
0180     return totalSize;
0181 }
0182 
0183 KIO::StatDetails ArchiveProtocolBase::getStatDetails()
0184 {
0185     // takes care of converting old metadata details to new StatDetails
0186     // TODO KF6 : remove legacy "details" code path
0187     KIO::StatDetails details;
0188 #if KIO_VERSION < QT_VERSION_CHECK(5, 240, 0)
0189     if (hasMetaData(QStringLiteral("statDetails"))) {
0190 #endif
0191         const QString statDetails = metaData(QStringLiteral("statDetails"));
0192         details = statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt());
0193 #if KIO_VERSION < QT_VERSION_CHECK(5, 240, 0)
0194     } else {
0195         const QString sDetails = metaData(QStringLiteral("details"));
0196         // silence deprecation warning for KIO::detailsToStatDetails
0197         QT_WARNING_PUSH
0198         QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0199         QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0200         details = sDetails.isEmpty() ? KIO::StatDefaultDetails : KIO::detailsToStatDetails(sDetails.toInt());
0201         QT_WARNING_POP
0202     }
0203 #endif
0204     return details;
0205 }
0206 
0207 void ArchiveProtocolBase::createRootUDSEntry(KIO::UDSEntry &entry)
0208 {
0209     entry.clear();
0210     entry.reserve(7);
0211 
0212     auto path = m_archiveFile->fileName();
0213     path = path.mid(path.lastIndexOf(QLatin1Char('/')) + 1);
0214 
0215     entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
0216     entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, path);
0217     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0218     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, m_mtime);
0219     // entry.fastInsert( KIO::UDSEntry::UDS_ACCESS, 07777 ); // fake 'x' permissions, this is a pseudo-directory
0220     entry.fastInsert(KIO::UDSEntry::UDS_USER, m_user);
0221     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_group);
0222 
0223     QMimeDatabase db;
0224     QMimeType mt = db.mimeTypeForFile(m_archiveFile->fileName());
0225     if (mt.isValid()) {
0226         entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mt.name());
0227     }
0228 }
0229 
0230 void ArchiveProtocolBase::createUDSEntry(const KArchiveEntry *archiveEntry, UDSEntry &entry)
0231 {
0232     entry.clear();
0233 
0234     entry.reserve(8);
0235     entry.fastInsert(KIO::UDSEntry::UDS_NAME, archiveEntry->name());
0236     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, archiveEntry->isFile() ? archiveEntry->permissions() & S_IFMT : S_IFDIR); // keep file type only
0237     if (archiveEntry->isFile()) {
0238         entry.fastInsert(KIO::UDSEntry::UDS_SIZE, ((KArchiveFile *)archiveEntry)->size());
0239     }
0240     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, archiveEntry->date().toSecsSinceEpoch());
0241     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, archiveEntry->permissions() & 07777); // keep permissions only
0242     entry.fastInsert(KIO::UDSEntry::UDS_USER, archiveEntry->user());
0243     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, archiveEntry->group());
0244     entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, archiveEntry->symLinkTarget());
0245 }
0246 
0247 KIO::WorkerResult ArchiveProtocolBase::listDir(const QUrl &url)
0248 {
0249     qCDebug(KIO_ARCHIVE_LOG) << url.url();
0250 
0251     QString path;
0252     KIO::Error errorNum;
0253     if (!checkNewFile(url, path, errorNum)) {
0254         if (errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING) {
0255             // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
0256             // Therefore give a more specific error message
0257             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0258                                            i18n("Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString()));
0259         } else if (errorNum != ERR_IS_DIRECTORY) {
0260             // We have any other error
0261             return KIO::WorkerResult::fail(errorNum, url.toDisplayString());
0262         }
0263         // It's a real dir -> redirect
0264         QUrl redir = QUrl::fromLocalFile(url.path());
0265         qCDebug(KIO_ARCHIVE_LOG) << "Ok, redirection to" << redir.url();
0266         redirection(redir);
0267         // And let go of the tar file - for people who want to unmount a cdrom after that
0268         delete m_archiveFile;
0269         m_archiveFile = nullptr;
0270         return KIO::WorkerResult::pass();
0271     }
0272 
0273     if (path.isEmpty()) {
0274         QUrl redir;
0275         redir.setScheme(url.scheme());
0276         qCDebug(KIO_ARCHIVE_LOG) << "url.path()=" << url.path();
0277         redir.setPath(url.path() + QLatin1Char('/'));
0278         qCDebug(KIO_ARCHIVE_LOG) << "redirection" << redir.url();
0279         redirection(redir);
0280         return KIO::WorkerResult::pass();
0281     }
0282 
0283     qCDebug(KIO_ARCHIVE_LOG) << "checkNewFile done";
0284     const KArchiveDirectory *root = m_archiveFile->directory();
0285     const KArchiveDirectory *dir;
0286     if (!path.isEmpty() && path != QLatin1String("/")) {
0287         qCDebug(KIO_ARCHIVE_LOG) << "Looking for entry" << path;
0288         const KArchiveEntry *e = root->entry(path);
0289         if (!e) {
0290             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
0291         }
0292         if (!e->isDirectory()) {
0293             return KIO::WorkerResult::fail(KIO::ERR_IS_FILE, url.toDisplayString());
0294         }
0295         dir = (KArchiveDirectory *)e;
0296     } else {
0297         dir = root;
0298     }
0299 
0300     const QStringList l = dir->entries();
0301     totalSize(l.count());
0302 
0303     UDSEntry entry;
0304     if (!l.contains(QLatin1String("."))) {
0305         createRootUDSEntry(entry);
0306         listEntry(entry);
0307     }
0308 
0309     QStringList::const_iterator it = l.begin();
0310     for (; it != l.end(); ++it) {
0311         qCDebug(KIO_ARCHIVE_LOG) << (*it);
0312         const KArchiveEntry *archiveEntry = dir->entry((*it));
0313 
0314         createUDSEntry(archiveEntry, entry);
0315 
0316         listEntry(entry);
0317     }
0318 
0319     qCDebug(KIO_ARCHIVE_LOG) << "done";
0320     return KIO::WorkerResult::pass();
0321 }
0322 
0323 KIO::WorkerResult ArchiveProtocolBase::stat(const QUrl &url)
0324 {
0325     QString path;
0326     UDSEntry entry;
0327     KIO::Error errorNum;
0328     if (!checkNewFile(url, path, errorNum)) {
0329         // We may be looking at a real directory - this happens
0330         // when pressing up after being in the root of an archive
0331         if (errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING) {
0332             // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
0333             // Therefore give a more specific error message
0334             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0335                                            i18n("Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString()));
0336         } else if (errorNum != ERR_IS_DIRECTORY) {
0337             // We have any other error
0338             return KIO::WorkerResult::fail(errorNum, url.toDisplayString());
0339         }
0340         entry.reserve(2);
0341         // Real directory. Return just enough information for KRun to work
0342         entry.fastInsert(KIO::UDSEntry::UDS_NAME, url.fileName());
0343         qCDebug(KIO_ARCHIVE_LOG) << "returning name" << url.fileName();
0344 
0345         QT_STATBUF buff;
0346 #ifdef Q_OS_WIN
0347         QString fullPath = url.path().remove(0, 1);
0348 #else
0349         QString fullPath = url.path();
0350 #endif
0351 
0352         if (QT_STAT(QFile::encodeName(fullPath).constData(), &buff) == -1) {
0353             // Should not happen, as the file was already stated by checkNewFile
0354             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.toDisplayString());
0355         }
0356 
0357         entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
0358 
0359         statEntry(entry);
0360 
0361         // And let go of the tar file - for people who want to unmount a cdrom after that
0362         delete m_archiveFile;
0363         m_archiveFile = nullptr;
0364         return KIO::WorkerResult::pass();
0365     }
0366 
0367     const KArchiveDirectory *root = m_archiveFile->directory();
0368     const KArchiveEntry *archiveEntry;
0369     if (path.isEmpty()) {
0370         path = QStringLiteral("/");
0371         archiveEntry = root;
0372     } else {
0373         archiveEntry = root->entry(path);
0374     }
0375     if (!archiveEntry) {
0376         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
0377     }
0378 
0379     if (archiveEntry == root) {
0380         createRootUDSEntry(entry);
0381     } else {
0382         createUDSEntry(archiveEntry, entry);
0383     }
0384 
0385     if (archiveEntry->isDirectory()) {
0386         auto details = getStatDetails();
0387         if (details & KIO::StatRecursiveSize) {
0388             const auto directoryEntry = static_cast<const KArchiveDirectory *>(archiveEntry);
0389             entry.fastInsert(KIO::UDSEntry::UDS_RECURSIVE_SIZE, static_cast<long long>(computeArchiveDirSize(directoryEntry)));
0390         }
0391     }
0392     statEntry(entry);
0393 
0394     return KIO::WorkerResult::pass();
0395 }
0396 
0397 KIO::WorkerResult ArchiveProtocolBase::get(const QUrl &url)
0398 {
0399     qCDebug(KIO_ARCHIVE_LOG) << url.url();
0400 
0401     QString path;
0402     KIO::Error errorNum;
0403     if (!checkNewFile(url, path, errorNum)) {
0404         if (errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING) {
0405             // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
0406             // Therefore give a more specific error message
0407             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0408                                            i18n("Could not open the file, probably due to an unsupported file format.\n%1", url.toDisplayString()));
0409         } else {
0410             // We have any other error
0411             return KIO::WorkerResult::fail(errorNum, url.toDisplayString());
0412         }
0413     }
0414 
0415     const KArchiveDirectory *root = m_archiveFile->directory();
0416     const KArchiveEntry *archiveEntry = root->entry(path);
0417 
0418     if (!archiveEntry) {
0419         return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
0420     }
0421     if (archiveEntry->isDirectory()) {
0422         return KIO::WorkerResult::fail(KIO::ERR_IS_DIRECTORY, url.toDisplayString());
0423     }
0424     const KArchiveFile *archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry);
0425     if (!archiveEntry->symLinkTarget().isEmpty()) {
0426         const QString target = archiveEntry->symLinkTarget();
0427         qCDebug(KIO_ARCHIVE_LOG) << "Redirection to" << target;
0428         const QUrl realURL = url.resolved(QUrl(target));
0429         qCDebug(KIO_ARCHIVE_LOG) << "realURL=" << realURL;
0430         redirection(realURL);
0431         return KIO::WorkerResult::pass();
0432     }
0433 
0434     // qCDebug(KIO_ARCHIVE_LOG) << "Preparing to get the archive data";
0435 
0436     /*
0437      * The easy way would be to get the data by calling archiveFileEntry->data()
0438      * However this has drawbacks:
0439      * - the complete file must be read into the memory
0440      * - errors are skipped, resulting in an empty file
0441      */
0442 
0443     std::unique_ptr<QIODevice> io(archiveFileEntry->createDevice());
0444 
0445     if (!io) {
0446         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED,
0447                                        i18n("The archive file could not be opened, perhaps because the format is unsupported.\n%1", url.toDisplayString()));
0448     }
0449 
0450     if (!io->open(QIODevice::ReadOnly)) {
0451         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString());
0452     }
0453 
0454     totalSize(archiveFileEntry->size());
0455 
0456     // Size of a QIODevice read. It must be large enough so that the mime type check will not fail
0457     const qint64 maxSize = 0x100000; // 1MB
0458 
0459     qint64 bufferSize = qMin(maxSize, archiveFileEntry->size());
0460     QByteArray buffer;
0461     buffer.resize(bufferSize);
0462     if (buffer.isEmpty() && bufferSize > 0) {
0463         // Something went wrong
0464         return KIO::WorkerResult::fail(KIO::ERR_OUT_OF_MEMORY, url.toDisplayString());
0465     }
0466 
0467     bool firstRead = true;
0468 
0469     // How much file do we still have to process?
0470     qint64 fileSize = archiveFileEntry->size();
0471     KIO::filesize_t processed = 0;
0472 
0473     while (!io->atEnd() && fileSize > 0) {
0474         if (!firstRead) {
0475             bufferSize = qMin(maxSize, fileSize);
0476             buffer.resize(bufferSize);
0477         }
0478         const qint64 read = io->read(buffer.data(), buffer.size()); // Avoid to use bufferSize here, in case something went wrong.
0479         if (read != bufferSize) {
0480             qCWarning(KIO_ARCHIVE_LOG) << "Read" << read << "bytes but expected" << bufferSize;
0481             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_READ, url.toDisplayString());
0482         }
0483         if (firstRead) {
0484             // We use the magic one the first data read
0485             // (As magic detection is about fixed positions, we can be sure that it is enough data.)
0486             QMimeDatabase db;
0487             QMimeType mime = db.mimeTypeForFileNameAndData(path, buffer);
0488             qCDebug(KIO_ARCHIVE_LOG) << "Emitting mimetype" << mime.name();
0489             mimeType(mime.name());
0490             firstRead = false;
0491         }
0492         data(buffer);
0493         processed += read;
0494         processedSize(processed);
0495         fileSize -= bufferSize;
0496     }
0497     io->close();
0498 
0499     data(QByteArray());
0500 
0501     return KIO::WorkerResult::pass();
0502 }
0503 
0504 /*
0505   In case someone wonders how the old filter stuff looked like :    :)
0506 void TARProtocol::slotData(void *_p, int _len)
0507 {
0508   switch (m_cmd) {
0509     case CMD_PUT:
0510       assert(m_pFilter);
0511       m_pFilter->send(_p, _len);
0512       break;
0513     default:
0514       abort();
0515       break;
0516     }
0517 }
0518 
0519 void TARProtocol::slotDataEnd()
0520 {
0521   switch (m_cmd) {
0522     case CMD_PUT:
0523       assert(m_pFilter && m_pJob);
0524       m_pFilter->finish();
0525       m_pJob->dataEnd();
0526       m_cmd = CMD_NONE;
0527       break;
0528     default:
0529       abort();
0530       break;
0531     }
0532 }
0533 
0534 void TARProtocol::jobData(void *_p, int _len)
0535 {
0536   switch (m_cmd) {
0537   case CMD_GET:
0538     assert(m_pFilter);
0539     m_pFilter->send(_p, _len);
0540     break;
0541   case CMD_COPY:
0542     assert(m_pFilter);
0543     m_pFilter->send(_p, _len);
0544     break;
0545   default:
0546     abort();
0547   }
0548 }
0549 
0550 void TARProtocol::jobDataEnd()
0551 {
0552   switch (m_cmd) {
0553   case CMD_GET:
0554     assert(m_pFilter);
0555     m_pFilter->finish();
0556     dataEnd();
0557     break;
0558   case CMD_COPY:
0559     assert(m_pFilter);
0560     m_pFilter->finish();
0561     m_pJob->dataEnd();
0562     break;
0563   default:
0564     abort();
0565   }
0566 }
0567 
0568 void TARProtocol::filterData(void *_p, int _len)
0569 {
0570 debug("void TARProtocol::filterData");
0571   switch (m_cmd) {
0572   case CMD_GET:
0573     data(_p, _len);
0574     break;
0575   case CMD_PUT:
0576     assert (m_pJob);
0577     m_pJob->data(_p, _len);
0578     break;
0579   case CMD_COPY:
0580     assert(m_pJob);
0581     m_pJob->data(_p, _len);
0582     break;
0583   default:
0584     abort();
0585   }
0586 }
0587 */
0588 
0589 // kate: space-indent on; indent-width 4; replace-tabs on;