File indexing completed on 2024-12-01 05:20:02

0001 /*
0002     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0003     SPDX-FileCopyrightText: 2002 Szombathelyi György <gyurco@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
0005     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0006 
0007     This file is heavily based on ktar from kdelibs
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "iso.h"
0013 
0014 #include <zlib.h>
0015 
0016 // QtCore
0017 #include <QByteArray>
0018 #include <QDebug>
0019 #include <QDir>
0020 #include <QFile>
0021 #include <QMimeDatabase>
0022 #include <QMimeType>
0023 #include <qplatformdefs.h>
0024 
0025 #include "../../app/compat.h"
0026 #include "kiso.h"
0027 #include "kisodirectory.h"
0028 #include "kisofile.h"
0029 #include "libisofs/iso_fs.h"
0030 
0031 // Pseudo plugin class to embed meta data
0032 class KIOPluginForMetaData : public QObject
0033 {
0034     Q_OBJECT
0035     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.iso" FILE "iso.json")
0036 };
0037 
0038 using namespace KIO;
0039 extern "C" {
0040 
0041 int Q_DECL_EXPORT kdemain(int argc, char **argv)
0042 {
0043     // qDebug()   << "Starting " << getpid() << endl;
0044 
0045     if (argc != 4) {
0046         fprintf(stderr, "Usage: kio_iso protocol domain-socket1 domain-socket2\n");
0047         exit(-1);
0048     }
0049 
0050     kio_isoProtocol slave(argv[2], argv[3]);
0051     slave.dispatchLoop();
0052 
0053     // qDebug()   << "Done" << endl;
0054     return 0;
0055 }
0056 
0057 } // extern "C"
0058 
0059 typedef struct {
0060     char magic[8];
0061     char uncompressed_len[4];
0062     unsigned char header_size;
0063     unsigned char block_size;
0064     char reserved[2]; /* Reserved for future use, MBZ */
0065 } compressed_file_header;
0066 
0067 static const unsigned char zisofs_magic[8] = {0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07};
0068 
0069 kio_isoProtocol::kio_isoProtocol(const QByteArray &pool, const QByteArray &app)
0070     : SlaveBase("iso", pool, app)
0071 {
0072     // qDebug() << "kio_isoProtocol::kio_isoProtocol" << endl;
0073     m_isoFile = nullptr;
0074 }
0075 
0076 kio_isoProtocol::~kio_isoProtocol()
0077 {
0078     delete m_isoFile;
0079 }
0080 
0081 bool kio_isoProtocol::checkNewFile(QString fullPath, QString &path, int startsec)
0082 {
0083     // qDebug()   << "kio_isoProtocol::checkNewFile " << fullPath << " startsec: " <<
0084     // startsec << endl;
0085 
0086     // Are we already looking at that file ?
0087     if (m_isoFile && startsec == m_isoFile->startSec() && m_isoFile->fileName() == fullPath.left(m_isoFile->fileName().length())) {
0088         // Has it changed ?
0089         QT_STATBUF statbuf;
0090         if (QT_STAT(QFile::encodeName(m_isoFile->fileName()), &statbuf) == 0) {
0091             if (m_mtime == statbuf.st_mtime) {
0092                 path = fullPath.mid(m_isoFile->fileName().length());
0093                 // qDebug()   << "kio_isoProtocol::checkNewFile returning " << path << endl;
0094                 if (path.endsWith(DIR_SEPARATOR_CHAR)) {
0095                     path.chop(1);
0096                 }
0097                 if (path.isEmpty()) {
0098                     path = DIR_SEPARATOR_CHAR;
0099                 }
0100                 return true;
0101             }
0102         }
0103     }
0104     // qDebug() << "Need to open a new file" << endl;
0105 
0106     // Close previous file
0107     if (m_isoFile) {
0108         m_isoFile->close();
0109         delete m_isoFile;
0110         m_isoFile = nullptr;
0111     }
0112 
0113     // Find where the iso file is in the full path
0114     int pos = 0;
0115     QString isoFile;
0116     path.clear();
0117 
0118     int len = fullPath.length();
0119     if (len != 0 && fullPath[len - 1] != DIR_SEPARATOR_CHAR)
0120         fullPath += DIR_SEPARATOR_CHAR;
0121 
0122     // qDebug()   << "the full path is " << fullPath << endl;
0123     while ((pos = fullPath.indexOf(DIR_SEPARATOR_CHAR, pos + 1)) != -1) {
0124         QString tryPath = fullPath.left(pos);
0125         // qDebug()   << fullPath << "  trying " << tryPath << endl;
0126 
0127         QT_STATBUF statbuf;
0128         if (QT_LSTAT(QFile::encodeName(tryPath), &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) {
0129             bool isFile = true;
0130             if (S_ISLNK(statbuf.st_mode)) {
0131                 char symDest[256];
0132                 memset(symDest, 0, 256);
0133                 ssize_t endOfName = readlink(QFile::encodeName(tryPath), symDest, 256);
0134                 if (endOfName != -1) {
0135                     if (QDir(QString::fromLocal8Bit(symDest)).exists())
0136                         isFile = false;
0137                 }
0138             }
0139 
0140             if (isFile) {
0141                 isoFile = tryPath;
0142                 m_mtime = statbuf.st_mtime;
0143                 m_mode = statbuf.st_mode;
0144                 path = fullPath.mid(pos + 1);
0145                 // qDebug()   << "fullPath=" << fullPath << " path=" << path << endl;
0146                 if (path.endsWith(DIR_SEPARATOR_CHAR)) {
0147                     path.chop(1);
0148                 }
0149                 if (path.isEmpty()) {
0150                     path = DIR_SEPARATOR_CHAR;
0151                 }
0152                 // qDebug()   << "Found. isoFile=" << isoFile << " path=" << path << endl;
0153                 break;
0154             }
0155         }
0156     }
0157     if (isoFile.isEmpty()) {
0158         // qDebug()   << "kio_isoProtocol::checkNewFile: not found" << endl;
0159         return false;
0160     }
0161 
0162     // Open new file
0163     // qDebug() << "Opening KIso on " << isoFile << endl;
0164     m_isoFile = new KIso(isoFile);
0165     m_isoFile->setStartSec(startsec);
0166     if (!m_isoFile->open(QIODevice::ReadOnly)) {
0167         // qDebug()   << "Opening " << isoFile << " failed." << endl;
0168         delete m_isoFile;
0169         m_isoFile = nullptr;
0170         return false;
0171     }
0172 
0173     return true;
0174 }
0175 
0176 void kio_isoProtocol::createUDSEntry(const KArchiveEntry *isoEntry, UDSEntry &entry)
0177 {
0178     entry.clear();
0179     entry.fastInsert(UDSEntry::UDS_NAME, isoEntry->name());
0180     entry.fastInsert(UDSEntry::UDS_FILE_TYPE, isoEntry->permissions() & S_IFMT); // keep file type only
0181     entry.fastInsert(UDSEntry::UDS_ACCESS, isoEntry->permissions() & 07777); // keep permissions only
0182 
0183     if (isoEntry->isFile()) {
0184         long long si = (dynamic_cast<const KIsoFile *>(isoEntry))->realsize();
0185         if (!si)
0186             si = (dynamic_cast<const KIsoFile *>(isoEntry))->size();
0187         entry.fastInsert(UDSEntry::UDS_SIZE, si);
0188     } else {
0189         entry.fastInsert(UDSEntry::UDS_SIZE, 0L);
0190     }
0191 
0192     entry.fastInsert(UDSEntry::UDS_USER, isoEntry->user());
0193     entry.fastInsert(UDSEntry::UDS_GROUP, isoEntry->group());
0194     entry.fastInsert((uint)UDSEntry::UDS_MODIFICATION_TIME, isoEntry->date().toTime_t());
0195     entry.fastInsert(UDSEntry::UDS_ACCESS_TIME,
0196                      isoEntry->isFile() ? (dynamic_cast<const KIsoFile *>(isoEntry))->adate() : (dynamic_cast<const KIsoDirectory *>(isoEntry))->adate());
0197 
0198     entry.fastInsert(UDSEntry::UDS_CREATION_TIME,
0199                      isoEntry->isFile() ? (dynamic_cast<const KIsoFile *>(isoEntry))->cdate() : (dynamic_cast<const KIsoDirectory *>(isoEntry))->cdate());
0200 
0201     entry.fastInsert(UDSEntry::UDS_LINK_DEST, isoEntry->symLinkTarget());
0202 }
0203 
0204 void kio_isoProtocol::listDir(const QUrl &url)
0205 {
0206     // qDebug() << "kio_isoProtocol::listDir " << url.url() << endl;
0207 
0208     QString path;
0209     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
0210         QByteArray _path(QFile::encodeName(getPath(url)));
0211         // qDebug()  << "Checking (stat) on " << _path << endl;
0212         QT_STATBUF buff;
0213         if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) {
0214             error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
0215             return;
0216         }
0217         // It's a real dir -> redirect
0218         QUrl redir;
0219         redir.setPath(getPath(url));
0220         if (url.hasFragment())
0221             redir.setFragment(url.fragment(QUrl::FullyDecoded));
0222         // qDebug()  << "Ok, redirection to " << redir.url() << endl;
0223         redir.setScheme("file");
0224         redirection(redir);
0225         finished();
0226         // And let go of the iso file - for people who want to unmount a cdrom after that
0227         delete m_isoFile;
0228         m_isoFile = nullptr;
0229         return;
0230     }
0231 
0232     if (path.isEmpty()) {
0233         QUrl redir(QStringLiteral("iso:/"));
0234         // qDebug() << "url.path()==" << getPath(url) << endl;
0235         if (url.hasFragment())
0236             redir.setFragment(url.fragment(QUrl::FullyDecoded));
0237         redir.setPath(getPath(url) + QString::fromLatin1(DIR_SEPARATOR));
0238         // qDebug() << "kio_isoProtocol::listDir: redirection " << redir.url() << endl;
0239         redir.setScheme("file");
0240         redirection(redir);
0241         finished();
0242         return;
0243     }
0244 
0245     // qDebug()  << "checkNewFile done" << endl;
0246     const KArchiveDirectory *root = m_isoFile->directory();
0247     const KArchiveDirectory *dir;
0248     if (!path.isEmpty() && path != DIR_SEPARATOR) {
0249         // qDebug()   << QString("Looking for entry %1").arg(path) << endl;
0250         const KArchiveEntry *e = root->entry(path);
0251         if (!e) {
0252             error(KIO::ERR_DOES_NOT_EXIST, path);
0253             return;
0254         }
0255         if (!e->isDirectory()) {
0256             error(KIO::ERR_IS_FILE, path);
0257             return;
0258         }
0259         dir = dynamic_cast<const KArchiveDirectory *>(e);
0260     } else {
0261         dir = root;
0262     }
0263 
0264     QStringList l = dir->entries();
0265     totalSize(l.count());
0266 
0267     UDSEntry entry;
0268     QStringList::Iterator it = l.begin();
0269     for (; it != l.end(); ++it) {
0270         // qDebug()   << (*it) << endl;
0271         const KArchiveEntry *isoEntry = dir->entry((*it));
0272 
0273         createUDSEntry(isoEntry, entry);
0274 
0275         listEntry(entry);
0276     }
0277 
0278     finished();
0279     // qDebug()  << "kio_isoProtocol::listDir done" << endl;
0280 }
0281 
0282 void kio_isoProtocol::stat(const QUrl &url)
0283 {
0284     QString path;
0285     UDSEntry entry;
0286 
0287     // qDebug() << "kio_isoProtocol::stat " << url.url() << endl;
0288     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
0289         // We may be looking at a real directory - this happens
0290         // when pressing up after being in the root of an archive
0291         QByteArray _path(QFile::encodeName(getPath(url)));
0292         // qDebug()  << "kio_isoProtocol::stat (stat) on " << _path << endl;
0293         QT_STATBUF buff;
0294         if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) {
0295             // qDebug() << "isdir=" << S_ISDIR(buff.st_mode) << "  errno=" << strerror(errno) << endl;
0296             error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
0297             return;
0298         }
0299         // Real directory. Return just enough information for KRun to work
0300         entry.fastInsert(UDSEntry::UDS_NAME, url.fileName());
0301         // qDebug()  << "kio_isoProtocol::stat returning name=" << url.fileName() << endl;
0302 
0303         entry.fastInsert(UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
0304 
0305         statEntry(entry);
0306 
0307         finished();
0308 
0309         // And let go of the iso file - for people who want to unmount a cdrom after that
0310         delete m_isoFile;
0311         m_isoFile = nullptr;
0312         return;
0313     }
0314 
0315     const KArchiveDirectory *root = m_isoFile->directory();
0316     const KArchiveEntry *isoEntry;
0317     if (path.isEmpty()) {
0318         path = QString::fromLatin1(DIR_SEPARATOR);
0319         isoEntry = root;
0320     } else {
0321         isoEntry = root->entry(path);
0322     }
0323     if (!isoEntry) {
0324         error(KIO::ERR_DOES_NOT_EXIST, path);
0325         return;
0326     }
0327     createUDSEntry(isoEntry, entry);
0328     statEntry(entry);
0329     finished();
0330 }
0331 
0332 void kio_isoProtocol::getFile(const KIsoFile *isoFileEntry, const QString &path)
0333 {
0334     unsigned long long size, pos = 0;
0335     bool mime = false, zlib = false;
0336     QByteArray fileData, pointer_block, inbuf, outbuf;
0337     char *pptr = nullptr;
0338     compressed_file_header *hdr;
0339     int block_shift;
0340     unsigned long nblocks;
0341     unsigned long fullsize = 0, block_size = 0, block_size2 = 0;
0342     size_t ptrblock_bytes;
0343     unsigned long cstart, cend, csize;
0344     uLong bytes;
0345 
0346     size = isoFileEntry->realsize();
0347     if (size >= sizeof(compressed_file_header))
0348         zlib = true;
0349     if (!size)
0350         size = isoFileEntry->size();
0351     totalSize(size);
0352     if (!size)
0353         mimeType("application/x-zerosize");
0354 
0355     if (size && !m_isoFile->device()->isOpen()) {
0356         m_isoFile->device()->open(QIODevice::ReadOnly);
0357 
0358         // seek(0) ensures integrity with the QIODevice's built-in buffer
0359         // see bug #372023 for details
0360         m_isoFile->device()->seek(0);
0361     }
0362 
0363     if (zlib) {
0364         fileData = isoFileEntry->dataAt(0, sizeof(compressed_file_header));
0365         if (fileData.size() == sizeof(compressed_file_header) && !memcmp(fileData.data(), zisofs_magic, sizeof(zisofs_magic))) {
0366             hdr = (compressed_file_header *)fileData.data();
0367             block_shift = hdr->block_size;
0368             block_size = 1UL << block_shift;
0369             block_size2 = block_size << 1;
0370             fullsize = isonum_731(hdr->uncompressed_len);
0371             nblocks = (fullsize + block_size - 1) >> block_shift;
0372             ptrblock_bytes = (nblocks + 1) * 4;
0373             pointer_block = isoFileEntry->dataAt(hdr->header_size << 2, ptrblock_bytes);
0374             if ((unsigned long)pointer_block.size() == ptrblock_bytes) {
0375                 inbuf.resize(static_cast<int>(block_size2));
0376                 if (inbuf.size()) {
0377                     outbuf.resize(static_cast<int>(block_size));
0378 
0379                     if (outbuf.size())
0380                         pptr = pointer_block.data();
0381                     else {
0382                         error(KIO::ERR_CANNOT_READ, path);
0383                         return;
0384                     }
0385                 } else {
0386                     error(KIO::ERR_CANNOT_READ, path);
0387                     return;
0388                 }
0389             } else {
0390                 error(KIO::ERR_CANNOT_READ, path);
0391                 return;
0392             }
0393         } else {
0394             zlib = false;
0395         }
0396     }
0397 
0398     while (pos < size) {
0399         if (zlib) {
0400             cstart = isonum_731(pptr);
0401             pptr += 4;
0402             cend = isonum_731(pptr);
0403 
0404             csize = cend - cstart;
0405 
0406             if (csize == 0) {
0407                 outbuf.fill(0, -1);
0408             } else {
0409                 if (csize > block_size2) {
0410                     // err = EX_DATAERR;
0411                     break;
0412                 }
0413 
0414                 inbuf = isoFileEntry->dataAt(cstart, csize);
0415                 if ((unsigned long)inbuf.size() != csize) {
0416                     break;
0417                 }
0418 
0419                 bytes = block_size; // Max output buffer size
0420                 if ((uncompress((Bytef *)outbuf.data(), &bytes, (Bytef *)inbuf.data(), csize)) != Z_OK) {
0421                     break;
0422                 }
0423             }
0424 
0425             if (((fullsize > block_size) && (bytes != block_size)) || ((fullsize <= block_size) && (bytes < fullsize))) {
0426                 break;
0427             }
0428 
0429             if (bytes > fullsize)
0430                 bytes = fullsize;
0431             fileData = outbuf;
0432             fileData.resize(static_cast<int>(bytes));
0433             fullsize -= bytes;
0434         } else {
0435             fileData = isoFileEntry->dataAt(pos, 65536);
0436             if (fileData.size() == 0)
0437                 break;
0438         }
0439         if (!mime) {
0440             QMimeDatabase db;
0441             QMimeType mt = db.mimeTypeForFileNameAndData(path, fileData);
0442             if (mt.isValid()) {
0443                 // qDebug() << "Emitting mimetype " << mt.name() << endl;
0444                 mimeType(mt.name());
0445                 mime = true;
0446             }
0447         }
0448         data(fileData);
0449         pos += fileData.size();
0450         processedSize(pos);
0451     }
0452 
0453     if (pos != size) {
0454         error(KIO::ERR_CANNOT_READ, path);
0455         return;
0456     }
0457 
0458     fileData.resize(0);
0459     data(fileData);
0460     processedSize(pos);
0461     finished();
0462 }
0463 
0464 void kio_isoProtocol::get(const QUrl &url)
0465 {
0466     // qDebug()  << "kio_isoProtocol::get" << url.url() << endl;
0467 
0468     QString path;
0469     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
0470         error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
0471         return;
0472     }
0473 
0474     const KArchiveDirectory *root = m_isoFile->directory();
0475     const KArchiveEntry *isoEntry = root->entry(path);
0476 
0477     if (!isoEntry) {
0478         error(KIO::ERR_DOES_NOT_EXIST, path);
0479         return;
0480     }
0481     if (isoEntry->isDirectory()) {
0482         error(KIO::ERR_IS_DIRECTORY, path);
0483         return;
0484     }
0485 
0486     const auto *isoFileEntry = dynamic_cast<const KIsoFile *>(isoEntry);
0487     if (!isoEntry->symLinkTarget().isEmpty()) {
0488         // qDebug() << "Redirection to " << isoEntry->symLinkTarget() << endl;
0489         QUrl realURL = QUrl(url).resolved(QUrl(isoEntry->symLinkTarget()));
0490         // qDebug() << "realURL= " << realURL.url() << endl;
0491         realURL.setScheme("file");
0492         redirection(realURL);
0493         finished();
0494         return;
0495     }
0496     getFile(isoFileEntry, path);
0497     if (m_isoFile->device()->isOpen())
0498         m_isoFile->device()->close();
0499 }
0500 
0501 QString kio_isoProtocol::getPath(const QUrl &url)
0502 {
0503     QString path = url.path();
0504     REPLACE_DIR_SEP2(path);
0505 
0506 #ifdef Q_OS_WIN
0507     if (path.startsWith(DIR_SEPARATOR)) {
0508         int p = 1;
0509         while (p < path.length() && path[p] == DIR_SEPARATOR_CHAR)
0510             p++;
0511         /* /C:/Folder */
0512         if (p + 2 <= path.length() && path[p].isLetter() && path[p + 1] == ':') {
0513             path = path.mid(p);
0514         }
0515     }
0516 #endif
0517     return path;
0518 }
0519 
0520 #include "iso.moc"