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 "kiso.h"
0013 
0014 // QtCore
0015 #include <QDebug>
0016 #include <QDir>
0017 #include <QFile>
0018 #include <QMimeDatabase>
0019 #include <QMimeType>
0020 #include <qplatformdefs.h>
0021 
0022 #include <KArchive/KFilterBase>
0023 #include <KArchive/KFilterDev>
0024 #include <KConfigCore/KConfig>
0025 #include <KConfigCore/KConfigGroup>
0026 
0027 #include "libisofs/isofs.h"
0028 #include "qfilehack.h"
0029 
0030 #ifdef Q_OS_LINUX
0031 #undef __STRICT_ANSI__
0032 #include <linux/cdrom.h>
0033 #define __STRICT_ANSI__
0034 #include <fcntl.h>
0035 #include <sys/ioctl.h>
0036 #endif
0037 
0038 ////////////////////////////////////////////////////////////////////////
0039 /////////////////////////// KIso ///////////////////////////////////
0040 ////////////////////////////////////////////////////////////////////////
0041 
0042 /**
0043  * puts the track layout of the device 'fname' into 'tracks'
0044  * tracks structure: start sector, track number, ...
0045  * tracks should be 100*2 entry long (this is the maximum in the CD-ROM standard)
0046  * currently it's linux only, porters are welcome
0047  */
0048 
0049 static int getTracks(const char *fname, int *tracks)
0050 {
0051     KRFUNC;
0052     int ret = 0;
0053     memset(tracks, 0, 200 * sizeof(int));
0054 
0055 #ifdef Q_OS_LINUX
0056     int fd, i;
0057     struct cdrom_tochdr tochead;
0058     struct cdrom_tocentry tocentry;
0059 
0060     // qDebug() << "getTracks open:" << fname << endl;
0061     fd = QT_OPEN(fname, O_RDONLY | O_NONBLOCK);
0062     if (fd > 0) {
0063         if (ioctl(fd, CDROMREADTOCHDR, &tochead) != -1) {
0064             //            qDebug() << "getTracks first track:" << tochead.cdth_trk0
0065             //            << " last track " << tochead.cdth_trk1 << endl;
0066             for (i = tochead.cdth_trk0; i <= tochead.cdth_trk1; ++i) {
0067                 if (ret > 99)
0068                     break;
0069                 memset(&tocentry, 0, sizeof(struct cdrom_tocentry));
0070                 tocentry.cdte_track = static_cast<__u8>(i);
0071                 tocentry.cdte_format = CDROM_LBA;
0072                 if (ioctl(fd, CDROMREADTOCENTRY, &tocentry) < 0)
0073                     break;
0074                 //                qDebug() << "getTracks got track " << i << " starting at: " <<
0075                 //                tocentry.cdte_addr.lba << endl;
0076                 if ((tocentry.cdte_ctrl & 0x4) == 0x4) {
0077                     tracks[ret << 1] = tocentry.cdte_addr.lba;
0078                     tracks[(ret << 1) + 1] = i;
0079                     ret++;
0080                 }
0081             }
0082         }
0083         close(fd);
0084     }
0085 
0086 #endif
0087 
0088     return ret;
0089 }
0090 
0091 class KIso::KIsoPrivate
0092 {
0093 public:
0094     KIsoPrivate() = default;
0095     QStringList dirList;
0096 };
0097 
0098 KIso::KIso(const QString &filename, const QString &_mimetype)
0099     : KArchive(nullptr)
0100 {
0101     KRFUNC;
0102     KRDEBUG("Starting KIso: " << filename << " - type: " << _mimetype);
0103 
0104     m_startsec = -1;
0105     m_filename = filename;
0106     d = new KIsoPrivate;
0107     QString mimetype(_mimetype);
0108     bool forced = true;
0109     if (mimetype.isEmpty()) {
0110         QMimeDatabase db;
0111         QMimeType mt = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent);
0112         if (mt.isValid())
0113             mimetype = mt.name();
0114 
0115         // qDebug() << "KIso::KIso mimetype=" << mimetype << endl;
0116 
0117         // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
0118         if (mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
0119             mimetype == "application/x-webarchive")
0120             // that's a gzipped tar file, so ask for gzip filter
0121             mimetype = "application/x-gzip";
0122         else if (mimetype == "application/x-tbz") // that's a bzipped2 tar file, so ask for bz2 filter
0123             mimetype = "application/x-bzip2";
0124         else {
0125             // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
0126             QFile file(filename);
0127             if (file.open(QIODevice::ReadOnly)) {
0128                 char firstByte;
0129                 char secondByte;
0130                 char thirdByte;
0131                 file.getChar(&firstByte);
0132                 file.getChar(&secondByte);
0133                 file.getChar(&thirdByte);
0134                 if (firstByte == 0037 && secondByte == (char)0213)
0135                     mimetype = "application/x-gzip";
0136                 else if (firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h')
0137                     mimetype = "application/x-bzip2";
0138                 else if (firstByte == 'P' && secondByte == 'K' && thirdByte == 3) {
0139                     char fourthByte;
0140                     file.getChar(&fourthByte);
0141                     if (fourthByte == 4)
0142                         mimetype = "application/x-zip";
0143                 }
0144             }
0145         }
0146         forced = false;
0147     }
0148 
0149     prepareDevice(filename, mimetype, forced);
0150 }
0151 
0152 void KIso::prepareDevice(const QString &filename, const QString &mimetype, bool forced)
0153 {
0154     KRFUNC;
0155     KRDEBUG("Preparing: " << filename << " - type: " << mimetype << " - using the force: " << forced);
0156     /* 'hack' for Qt's false assumption that only S_ISREG is seekable */
0157     if ("inode/blockdevice" == mimetype)
0158         setDevice(new QFileHack(filename));
0159     else {
0160         if ("application/x-gzip" == mimetype || "application/x-bzip2" == mimetype)
0161             forced = true;
0162 
0163         KCompressionDevice *device;
0164         if (mimetype.isEmpty()) {
0165             device = new KFilterDev(filename);
0166         } else {
0167             device = new KCompressionDevice(filename, COMPRESSIONTYPEFORMIMETYPE(mimetype));
0168         }
0169         if (device->compressionType() == KCompressionDevice::None && forced) {
0170             delete device;
0171         } else {
0172             setDevice(device);
0173         }
0174     }
0175 }
0176 
0177 KIso::KIso(QIODevice *dev)
0178     : KArchive(dev)
0179 {
0180     d = new KIsoPrivate;
0181 }
0182 
0183 KIso::~KIso()
0184 {
0185     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
0186     if (isOpen())
0187         close();
0188     if (!m_filename.isEmpty())
0189         delete device(); // we created it ourselves
0190     delete d;
0191 }
0192 
0193 /* callback function for libisofs */
0194 static int readf(char *buf, unsigned int start, unsigned int len, void *udata)
0195 {
0196     KRFUNC;
0197 
0198     QIODevice *dev = (static_cast<KIso *>(udata))->device();
0199 
0200     // seek(0) ensures integrity with the QIODevice's built-in buffer
0201     // see bug #372023 for details
0202     dev->seek(0);
0203 
0204     if (dev->seek((qint64)start << (qint64)11)) {
0205         if ((dev->read(buf, len << 11u)) != -1)
0206             return (len);
0207     }
0208     // qDebug() << "KIso::ReadRequest failed start: " << start << " len: " << len << endl;
0209 
0210     return -1;
0211 }
0212 
0213 /* callback function for libisofs */
0214 static int mycallb(struct iso_directory_record *idr, void *udata)
0215 {
0216     KRFUNC;
0217 
0218     auto *iso = static_cast<KIso *>(udata);
0219     QString path, user, group, symlink;
0220     int i;
0221     int access;
0222     time_t time, cdate, adate;
0223     rr_entry rr;
0224     bool special = false;
0225     KArchiveEntry *entry = nullptr, *oldentry = nullptr;
0226     char z_algo[2], z_params[2];
0227     long long z_size = 0;
0228 
0229     if ((idr->flags[0] & 1) && !iso->showhidden)
0230         return 0;
0231     if (iso->level) {
0232         if (isonum_711(idr->name_len) == 1) {
0233             switch (idr->name[0]) {
0234             case 0:
0235                 path += (".");
0236                 special = true;
0237                 break;
0238             case 1:
0239                 path += ("..");
0240                 special = true;
0241                 break;
0242             }
0243         }
0244         if (iso->showrr && ParseRR(idr, &rr) > 0) {
0245             if (!special)
0246                 path = rr.name;
0247             symlink = rr.sl;
0248             access = rr.mode;
0249             time = rr.t_mtime;
0250             adate = rr.t_atime;
0251             cdate = rr.t_ctime;
0252             user.setNum(rr.uid);
0253             group.setNum(rr.gid);
0254             z_algo[0] = rr.z_algo[0];
0255             z_algo[1] = rr.z_algo[1];
0256             z_params[0] = rr.z_params[0];
0257             z_params[1] = rr.z_params[1];
0258             z_size = rr.z_size;
0259         } else {
0260             access = iso->dirent->permissions() & ~S_IFMT;
0261             adate = cdate = time = isodate_915(idr->date, 0);
0262             user = iso->dirent->user();
0263             group = iso->dirent->group();
0264             if (idr->flags[0] & 2)
0265                 access |= S_IFDIR;
0266             else
0267                 access |= S_IFREG;
0268             if (!special) {
0269                 if (iso->joliet) {
0270                     for (i = 0; i < (isonum_711(idr->name_len) - 1); i += 2) {
0271                         void *p = &(idr->name[i]);
0272                         QChar ch(be2me_16(*(ushort *)p));
0273                         if (ch == ';')
0274                             break;
0275                         path += ch;
0276                     }
0277                 } else {
0278                     for (i = 0; i < isonum_711(idr->name_len); ++i) {
0279                         if (idr->name[i] == ';')
0280                             break;
0281                         if (idr->name[i])
0282                             path += (idr->name[i]);
0283                     }
0284                 }
0285                 if (path.endsWith('.'))
0286                     path.resize(path.length() - 1);
0287             }
0288         }
0289         if (iso->showrr)
0290             FreeRR(&rr);
0291         if (idr->flags[0] & 2) {
0292             entry = new KIsoDirectory(iso, path, access | S_IFDIR, time, adate, cdate, user, group, symlink);
0293         } else {
0294             entry = new KIsoFile(iso,
0295                                  path,
0296                                  access,
0297                                  time,
0298                                  adate,
0299                                  cdate,
0300                                  user,
0301                                  group,
0302                                  symlink,
0303                                  (long long)(isonum_733(idr->extent)) << (long long)11,
0304                                  isonum_733(idr->size));
0305             if (z_size)
0306                 (dynamic_cast<KIsoFile *>(entry))->setZF(z_algo, z_params, z_size);
0307         }
0308         iso->dirent->addEntry(entry);
0309     }
0310     if ((idr->flags[0] & 2) && (iso->level == 0 || !special)) {
0311         if (iso->level) {
0312             oldentry = iso->dirent;
0313             iso->dirent = dynamic_cast<KIsoDirectory *>(entry);
0314         }
0315         iso->level++;
0316         ProcessDir(&readf, isonum_733(idr->extent), isonum_733(idr->size), &mycallb, udata);
0317         iso->level--;
0318         if (iso->level)
0319             iso->dirent = dynamic_cast<KIsoDirectory *>(oldentry);
0320     }
0321     return 0;
0322 }
0323 
0324 void KIso::addBoot(struct el_torito_boot_descriptor *bootdesc)
0325 {
0326     KRFUNC;
0327 
0328     int i;
0329     long long size;
0330     boot_head boot;
0331     boot_entry *be;
0332     QString path;
0333     KIsoFile *entry;
0334 
0335     path = "Catalog";
0336     entry = new KIsoFile(this,
0337                          path,
0338                          dirent->permissions() & ~S_IFDIR,
0339                          dirent->date(),
0340                          dirent->adate(),
0341                          dirent->cdate(),
0342                          dirent->user(),
0343                          dirent->group(),
0344                          QString(),
0345                          (long long)isonum_731(bootdesc->boot_catalog) << (long long)11,
0346                          (long long)2048);
0347     dirent->addEntry(entry);
0348     if (!ReadBootTable(&readf, isonum_731(bootdesc->boot_catalog), &boot, this)) {
0349         i = 1;
0350         be = boot.defentry;
0351         while (be) {
0352             size = BootImageSize(isonum_711(be->data.d_e.media), isonum_721(be->data.d_e.seccount));
0353             path = "Default Image";
0354             if (i > 1)
0355                 path += " (" + QString::number(i) + ')';
0356             entry = new KIsoFile(this,
0357                                  path,
0358                                  dirent->permissions() & ~S_IFDIR,
0359                                  dirent->date(),
0360                                  dirent->adate(),
0361                                  dirent->cdate(),
0362                                  dirent->user(),
0363                                  dirent->group(),
0364                                  QString(),
0365                                  (long long)isonum_731(be->data.d_e.start) << (long long)11,
0366                                  size << (long long)9);
0367             dirent->addEntry(entry);
0368             be = be->next;
0369             i++;
0370         }
0371 
0372         FreeBootTable(&boot);
0373     }
0374 }
0375 
0376 void KIso::readParams()
0377 {
0378     KRFUNC;
0379     KConfig *config;
0380 
0381     config = new KConfig("kio_isorc");
0382 
0383     KConfigGroup group(config, QString());
0384     showhidden = group.readEntry("showhidden", false);
0385     showrr = group.readEntry("showrr", true);
0386     delete config;
0387 }
0388 
0389 bool KIso::openArchive(QIODevice::OpenMode mode)
0390 {
0391     KRFUNC;
0392     iso_vol_desc *desc;
0393     QString path, uid, gid;
0394     QT_STATBUF buf;
0395     int tracks[2 * 100], trackno = 0, i, access, c_b, c_i, c_j;
0396     KArchiveDirectory *root;
0397     struct iso_directory_record *idr;
0398     struct el_torito_boot_descriptor *bootdesc;
0399 
0400     if (mode == QIODevice::WriteOnly)
0401         return false;
0402 
0403     readParams();
0404     d->dirList.clear();
0405 
0406     tracks[0] = 0;
0407     if (m_startsec > 0)
0408         tracks[0] = m_startsec;
0409     // qDebug() << " m_startsec: " << m_startsec << endl;
0410     /* We'll use the permission and user/group of the 'host' file except
0411      * in Rock Ridge, where the permissions are stored on the file system
0412      */
0413     if (QT_STAT(m_filename.toLocal8Bit(), &buf) < 0) {
0414         /* defaults, if stat fails */
0415         memset(&buf, 0, sizeof(struct stat));
0416         buf.st_mode = 0777;
0417     } else {
0418         /* If it's a block device, try to query the track layout (for multisession) */
0419         if (m_startsec == -1 && S_ISBLK(buf.st_mode))
0420             trackno = getTracks(m_filename.toLatin1(), (int *)&tracks);
0421     }
0422     uid.setNum(buf.st_uid);
0423     gid.setNum(buf.st_gid);
0424     access = buf.st_mode & ~S_IFMT;
0425 
0426     root = new KIsoDirectory(this, QStringLiteral("/"), 0777 | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
0427     setRootDir(root);
0428 
0429     // qDebug() << "KIso::openArchive number of tracks: " << trackno << endl;
0430 
0431     if (trackno == 0)
0432         trackno = 1;
0433     for (i = 0; i < trackno; ++i) {
0434         c_b = 1;
0435         c_i = 1;
0436         c_j = 1;
0437         root = rootDir();
0438         if (trackno > 1) {
0439             path.clear();
0440             QTextStream(&path) << "Track " << tracks[(i << 1) + 1];
0441             root = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
0442             rootDir()->addEntry(root);
0443         }
0444 
0445         desc = ReadISO9660(&readf, tracks[i << 1], this);
0446         if (!desc) {
0447             // qDebug() << "KIso::openArchive no volume descriptors" << endl;
0448             continue;
0449         }
0450 
0451         while (desc) {
0452             switch (isonum_711(desc->data.type)) {
0453             case ISO_VD_BOOT:
0454 
0455                 bootdesc = (struct el_torito_boot_descriptor *)&(desc->data);
0456                 if (!memcmp(EL_TORITO_ID, bootdesc->system_id, ISODCL(8, 39))) {
0457                     path = "El Torito Boot";
0458                     if (c_b > 1)
0459                         path += " (" + QString::number(c_b) + ')';
0460 
0461                     dirent = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
0462                     root->addEntry(dirent);
0463 
0464                     addBoot(bootdesc);
0465                     c_b++;
0466                 }
0467                 break;
0468 
0469             case ISO_VD_PRIMARY:
0470             case ISO_VD_SUPPLEMENTARY:
0471                 idr = (struct iso_directory_record *)&(((struct iso_primary_descriptor *)&desc->data)->root_directory_record);
0472                 joliet = JolietLevel(&desc->data);
0473                 if (joliet) {
0474                     QTextStream(&path) << "Joliet level " << joliet;
0475                     if (c_j > 1)
0476                         path += " (" + QString::number(c_j) + ')';
0477                 } else {
0478                     path = "ISO9660";
0479                     if (c_i > 1)
0480                         path += " (" + QString::number(c_i) + ')';
0481                 }
0482                 dirent = new KIsoDirectory(this, path, access | S_IFDIR, buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
0483                 root->addEntry(dirent);
0484                 level = 0;
0485                 mycallb(idr, this);
0486                 if (joliet)
0487                     c_j++;
0488                 else
0489                     c_i++;
0490                 break;
0491             }
0492             desc = desc->next;
0493         }
0494         free(desc);
0495     }
0496     device()->close();
0497     return true;
0498 }
0499 
0500 bool KIso::closeArchive()
0501 {
0502     KRFUNC;
0503     d->dirList.clear();
0504     return true;
0505 }
0506 
0507 bool KIso::writeDir(const QString &, const QString &, const QString &, mode_t, time_t, time_t, time_t)
0508 {
0509     return false;
0510 }
0511 
0512 bool KIso::prepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, time_t, time_t, time_t)
0513 {
0514     return false;
0515 }
0516 
0517 bool KIso::finishWriting(qint64)
0518 {
0519     return false;
0520 }
0521 
0522 bool KIso::writeSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, time_t, time_t, time_t)
0523 {
0524     return false;
0525 }
0526 
0527 bool KIso::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0528 {
0529     return false;
0530 }
0531 
0532 bool KIso::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0533 {
0534     return false;
0535 }
0536 
0537 bool KIso::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0538 {
0539     return false;
0540 }
0541 
0542 bool KIso::doFinishWriting(qint64)
0543 {
0544     return false;
0545 }
0546 
0547 void KIso::virtual_hook(int id, void *data)
0548 {
0549     KArchive::virtual_hook(id, data);
0550 }