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"