1 /*****************************************************************************
2  * Copyright (C) 2000 David Faure <faure@kde.org>                            *
3  * Copyright (C) 2002 Szombathelyi György <gyurco@users.sourceforge.net>     *
4  * Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>                       *
5  * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org]              *
6  *                                                                           *
7  * This file is heavily based on ktar from kdelibs                           *
8  *                                                                           *
9  * This file is part of Krusader [https://krusader.org].                     *
10  *                                                                           *
11  * Krusader is free software: you can redistribute it and/or modify          *
12  * it under the terms of the GNU General Public License as published by      *
13  * the Free Software Foundation, either version 2 of the License, or         *
14  * (at your option) any later version.                                       *
15  *                                                                           *
16  * Krusader is distributed in the hope that it will be useful,               *
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
19  * GNU General Public License for more details.                              *
20  *                                                                           *
21  * You should have received a copy of the GNU General Public License         *
22  * along with Krusader.  If not, see [http://www.gnu.org/licenses/].         *
23  *****************************************************************************/
24 
25 #include "kiso.h"
26 
27 // QtCore
28 #include <QDebug>
29 #include <QDir>
30 #include <QFile>
31 #include <QMimeDatabase>
32 #include <QMimeType>
33 #include <qplatformdefs.h>
34 
35 #include <KConfigCore/KConfig>
36 #include <KConfigCore/KConfigGroup>
37 #include <KArchive/KFilterBase>
38 #include <KArchive/KFilterDev>
39 
40 #include "libisofs/isofs.h"
41 #include "qfilehack.h"
42 
43 #ifdef Q_OS_LINUX
44 #undef __STRICT_ANSI__
45 #include <linux/cdrom.h>
46 #define __STRICT_ANSI__
47 #include <sys/ioctl.h>
48 #include <fcntl.h>
49 #endif
50 
51 ////////////////////////////////////////////////////////////////////////
52 /////////////////////////// KIso ///////////////////////////////////
53 ////////////////////////////////////////////////////////////////////////
54 
55 /**
56  * puts the track layout of the device 'fname' into 'tracks'
57  * tracks structure: start sector, track number, ...
58  * tracks should be 100*2 entry long (this is the maximum in the CD-ROM standard)
59  * currently it's linux only, porters are welcome
60  */
61 
getTracks(const char * fname,int * tracks)62 static int getTracks(const char *fname, int *tracks)
63 {
64     KRFUNC;
65     int ret = 0;
66     memset(tracks, 0, 200*sizeof(int));
67 
68 #ifdef Q_OS_LINUX
69     int fd, i;
70     struct cdrom_tochdr tochead;
71     struct cdrom_tocentry tocentry;
72 
73     //qDebug() << "getTracks open:" << fname << endl;
74     fd = QT_OPEN(fname, O_RDONLY | O_NONBLOCK);
75     if (fd > 0) {
76         if (ioctl(fd, CDROMREADTOCHDR, &tochead) != -1) {
77 //            qDebug() << "getTracks first track:" << tochead.cdth_trk0
78 //            << " last track " << tochead.cdth_trk1 << endl;
79             for (i = tochead.cdth_trk0;i <= tochead.cdth_trk1;++i) {
80                 if (ret > 99) break;
81                 memset(&tocentry, 0, sizeof(struct cdrom_tocentry));
82                 tocentry.cdte_track = i;
83                 tocentry.cdte_format = CDROM_LBA;
84                 if (ioctl(fd, CDROMREADTOCENTRY, &tocentry) < 0) break;
85 //                qDebug() << "getTracks got track " << i << " starting at: " <<
86 //                tocentry.cdte_addr.lba << endl;
87                 if ((tocentry.cdte_ctrl & 0x4) == 0x4) {
88                     tracks[ret<<1] = tocentry.cdte_addr.lba;
89                     tracks[(ret<<1)+1] = i;
90                     ret++;
91                 }
92             }
93         }
94         close(fd);
95     }
96 
97 #endif
98 
99     return ret;
100 }
101 
102 class KIso::KIsoPrivate
103 {
104 public:
KIsoPrivate()105     KIsoPrivate() {}
106     QStringList dirList;
107 };
108 
KIso(const QString & filename,const QString & _mimetype)109 KIso::KIso(const QString& filename, const QString & _mimetype)
110         : KArchive(0L)
111 {
112     KRFUNC;
113     KRDEBUG("Starting KIso: " << filename << " - type: " << _mimetype);
114 
115     m_startsec = -1;
116     m_filename = filename;
117     d = new KIsoPrivate;
118     QString mimetype(_mimetype);
119     bool forced = true;
120     if (mimetype.isEmpty()) {
121         QMimeDatabase db;
122         QMimeType mt = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent);
123         if (mt.isValid())
124             mimetype = mt.name();
125 
126         //qDebug() << "KIso::KIso mimetype=" << mimetype << endl;
127 
128         // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
129         if (mimetype == "application/x-tgz" || mimetype == "application/x-targz" ||  // the latter is deprecated but might still be around
130                 mimetype == "application/x-webarchive")
131             // that's a gzipped tar file, so ask for gzip filter
132             mimetype = "application/x-gzip";
133         else if (mimetype == "application/x-tbz")   // that's a bzipped2 tar file, so ask for bz2 filter
134             mimetype = "application/x-bzip2";
135         else {
136             // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
137             QFile file(filename);
138             if (file.open(QIODevice::ReadOnly)) {
139                 char firstByte;
140                 char secondByte;
141                 char thirdByte;
142                 file.getChar(&firstByte);
143                 file.getChar(&secondByte);
144                 file.getChar(&thirdByte);
145                 if (firstByte == 0037 && secondByte == (char)0213)
146                     mimetype = "application/x-gzip";
147                 else if (firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h')
148                     mimetype = "application/x-bzip2";
149                 else if (firstByte == 'P' && secondByte == 'K' && thirdByte == 3) {
150                     char fourthByte;
151                     file.getChar(&fourthByte);
152                     if (fourthByte == 4)
153                         mimetype = "application/x-zip";
154                 }
155             }
156         }
157         forced = false;
158     }
159 
160     prepareDevice(filename, mimetype, forced);
161 }
162 
prepareDevice(const QString & filename,const QString & mimetype,bool forced)163 void KIso::prepareDevice(const QString & filename,
164                          const QString & mimetype, bool forced)
165 {
166     KRFUNC;
167     KRDEBUG("Preparing: " << filename << " - type: " << mimetype << " - using the force: " << forced);
168     /* 'hack' for Qt's false assumption that only S_ISREG is seekable */
169     if ("inode/blockdevice" == mimetype)
170         setDevice(new QFileHack(filename));
171     else {
172         if ("application/x-gzip" == mimetype
173                 || "application/x-bzip2" == mimetype)
174             forced = true;
175 
176 
177         KCompressionDevice *device;
178         if(mimetype.isEmpty()) {
179             device = new KFilterDev(filename);
180         } else {
181             device = new KCompressionDevice(filename, KFilterDev::compressionTypeForMimeType(mimetype));
182         }
183         if (device->compressionType() == KCompressionDevice::None && forced) {
184             delete device;
185         } else {
186             setDevice(device);
187         }
188     }
189 
190 }
191 
KIso(QIODevice * dev)192 KIso::KIso(QIODevice * dev)
193         : KArchive(dev)
194 {
195     d = new KIsoPrivate;
196 }
197 
~KIso()198 KIso::~KIso()
199 {
200     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
201     if (isOpen())
202         close();
203     if (!m_filename.isEmpty())
204         delete device(); // we created it ourselves
205     delete d;
206 }
207 
208 /* callback function for libisofs */
readf(char * buf,unsigned int start,unsigned int len,void * udata)209 static int readf(char *buf, unsigned int start, unsigned int len, void *udata)
210 {
211     KRFUNC;
212 
213     QIODevice* dev = (static_cast<KIso*>(udata))->device();
214 
215     // seek(0) ensures integrity with the QIODevice's built-in buffer
216     // see bug #372023 for details
217     dev->seek(0);
218 
219     if (dev->seek((qint64)start << (qint64)11)) {
220         if ((dev->read(buf, len << 11u)) != -1) return (len);
221     }
222     //qDebug() << "KIso::ReadRequest failed start: " << start << " len: " << len << endl;
223 
224     return -1;
225 }
226 
227 /* callback function for libisofs */
mycallb(struct iso_directory_record * idr,void * udata)228 static int mycallb(struct iso_directory_record *idr, void *udata)
229 {
230     KRFUNC;
231 
232     KIso *iso = static_cast<KIso*>(udata);
233     QString path, user, group, symlink;
234     int i;
235     int access;
236     int time, cdate, adate;
237     rr_entry rr;
238     bool special = false;
239     KArchiveEntry *entry = NULL, *oldentry = NULL;
240     char z_algo[2], z_params[2];
241     long long z_size = 0;
242 
243     if ((idr->flags[0] & 1) && !iso->showhidden) return 0;
244     if (iso->level) {
245         if (isonum_711(idr->name_len) == 1) {
246             switch (idr->name[0]) {
247             case 0:
248                 path += (".");
249                 special = true;
250                 break;
251             case 1:
252                 path += ("..");
253                 special = true;
254                 break;
255             }
256         }
257         if (iso->showrr && ParseRR(idr, &rr) > 0) {
258             if (!special) path = rr.name;
259             symlink = rr.sl;
260             access = rr.mode;
261             time = rr.t_mtime;
262             adate = rr.t_atime;
263             cdate = rr.t_ctime;
264             user.setNum(rr.uid);
265             group.setNum(rr.gid);
266             z_algo[0] = rr.z_algo[0];z_algo[1] = rr.z_algo[1];
267             z_params[0] = rr.z_params[0];z_params[1] = rr.z_params[1];
268             z_size = rr.z_size;
269         } else {
270             access = iso->dirent->permissions() & ~S_IFMT;
271             adate = cdate = time = isodate_915(idr->date, 0);
272             user = iso->dirent->user();
273             group = iso->dirent->group();
274             if (idr->flags[0] & 2) access |= S_IFDIR; else access |= S_IFREG;
275             if (!special) {
276                 if (iso->joliet) {
277                     for (i = 0;i < (isonum_711(idr->name_len) - 1);i += 2) {
278                         void *p = &(idr->name[i]);
279                         QChar ch(be2me_16(*(ushort *)p));
280                         if (ch == ';') break;
281                         path += ch;
282                     }
283                 } else {
284                     for (i = 0;i < isonum_711(idr->name_len);++i) {
285                         if (idr->name[i] == ';') break;
286                         if (idr->name[i]) path += (idr->name[i]);
287                     }
288                 }
289                 if (path.endsWith('.')) path.resize(path.length() - 1);
290             }
291         }
292         if (iso->showrr) FreeRR(&rr);
293         if (idr->flags[0] & 2) {
294             entry = new KIsoDirectory(iso, path, access | S_IFDIR, time, adate, cdate,
295                                       user, group, symlink);
296         } else {
297             entry = new KIsoFile(iso, path, access, time, adate, cdate,
298                                  user, group, symlink, (long long)(isonum_733(idr->extent)) << (long long)11, isonum_733(idr->size));
299             if (z_size)(static_cast <KIsoFile*>(entry))->setZF(z_algo, z_params, z_size);
300 
301         }
302         iso->dirent->addEntry(entry);
303     }
304     if ((idr->flags[0] & 2) && (iso->level == 0 || !special)) {
305         if (iso->level) {
306             oldentry = iso->dirent;
307             iso->dirent = static_cast<KIsoDirectory*>(entry);
308         }
309         iso->level++;
310         ProcessDir(&readf, isonum_733(idr->extent), isonum_733(idr->size), &mycallb, udata);
311         iso->level--;
312         if (iso->level) iso->dirent = static_cast<KIsoDirectory*>(oldentry);
313     }
314     return 0;
315 }
316 
addBoot(struct el_torito_boot_descriptor * bootdesc)317 void KIso::addBoot(struct el_torito_boot_descriptor* bootdesc)
318 {
319     KRFUNC;
320 
321     int i;
322     long long size;
323     boot_head boot;
324     boot_entry *be;
325     QString path;
326     KIsoFile *entry;
327 
328     path = "Catalog";
329     entry = new KIsoFile(this, path, dirent->permissions() & ~S_IFDIR,
330                          dirent->date(), dirent->adate(), dirent->cdate(),
331                          dirent->user(), dirent->group(), QString(),
332                          (long long)isonum_731(bootdesc->boot_catalog) << (long long)11, (long long)2048);
333     dirent->addEntry(entry);
334     if (!ReadBootTable(&readf, isonum_731(bootdesc->boot_catalog), &boot, this)) {
335         i = 1;
336         be = boot.defentry;
337         while (be) {
338             size = BootImageSize(isonum_711(be->data.d_e.media),
339                                  isonum_721(be->data.d_e.seccount));
340             path = "Default Image";
341             if (i > 1) path += " (" + QString::number(i) + ')';
342             entry = new KIsoFile(this, path, dirent->permissions() & ~S_IFDIR,
343                                  dirent->date(), dirent->adate(), dirent->cdate(),
344                                  dirent->user(), dirent->group(), QString(),
345                                  (long long)isonum_731(be->data.d_e.start) << (long long)11, size << (long long)9);
346             dirent->addEntry(entry);
347             be = be->next;
348             i++;
349         }
350 
351         FreeBootTable(&boot);
352     }
353 }
354 
readParams()355 void KIso::readParams()
356 {
357     KRFUNC;
358     KConfig *config;
359 
360     config = new KConfig("kio_isorc");
361 
362     KConfigGroup group(config, QString());
363     showhidden = group.readEntry("showhidden", false);
364     showrr = group.readEntry("showrr", true);
365     delete config;
366 }
367 
openArchive(QIODevice::OpenMode mode)368 bool KIso::openArchive(QIODevice::OpenMode mode)
369 {
370     KRFUNC;
371     iso_vol_desc *desc;
372     QString path, uid, gid;
373     QT_STATBUF buf;
374     int tracks[2*100], trackno = 0, i, access, c_b, c_i, c_j;
375     KArchiveDirectory *root;
376     struct iso_directory_record* idr;
377     struct el_torito_boot_descriptor* bootdesc;
378 
379     if (mode == QIODevice::WriteOnly)
380         return false;
381 
382     readParams();
383     d->dirList.clear();
384 
385     tracks[0] = 0;
386     if (m_startsec > 0) tracks[0] = m_startsec;
387     //qDebug() << " m_startsec: " << m_startsec << endl;
388     /* We'll use the permission and user/group of the 'host' file except
389      * in Rock Ridge, where the permissions are stored on the file system
390      */
391     if (QT_STAT(m_filename.toLocal8Bit(), &buf) < 0) {
392         /* defaults, if stat fails */
393         memset(&buf, 0, sizeof(struct stat));
394         buf.st_mode = 0777;
395     } else {
396         /* If it's a block device, try to query the track layout (for multisession) */
397         if (m_startsec == -1 && S_ISBLK(buf.st_mode))
398             trackno = getTracks(m_filename.toLatin1(), (int*) & tracks);
399     }
400     uid.setNum(buf.st_uid);
401     gid.setNum(buf.st_gid);
402     access = buf.st_mode & ~S_IFMT;
403 
404     //qDebug() << "KIso::openArchive number of tracks: " << trackno << endl;
405 
406     if (trackno == 0) trackno = 1;
407     for (i = 0;i < trackno;++i) {
408 
409         c_b = 1;c_i = 1;c_j = 1;
410         root = rootDir();
411         if (trackno > 1) {
412             path.clear();
413             QTextStream(&path) << "Track " << tracks[(i<<1)+1];
414             root = new KIsoDirectory(this, path, access | S_IFDIR,
415                                      buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
416             rootDir()->addEntry(root);
417         }
418 
419         desc = ReadISO9660(&readf, tracks[i<<1], this);
420         if (!desc) {
421             //qDebug() << "KIso::openArchive no volume descriptors" << endl;
422             continue;
423         }
424 
425         while (desc) {
426             switch (isonum_711(desc->data.type)) {
427             case ISO_VD_BOOT:
428 
429                 bootdesc = (struct el_torito_boot_descriptor*) & (desc->data);
430                 if (!memcmp(EL_TORITO_ID, bootdesc->system_id, ISODCL(8, 39))) {
431                     path = "El Torito Boot";
432                     if (c_b > 1) path += " (" + QString::number(c_b) + ')';
433 
434                     dirent = new KIsoDirectory(this, path, access | S_IFDIR,
435                                                buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
436                     root->addEntry(dirent);
437 
438                     addBoot(bootdesc);
439                     c_b++;
440                 }
441                 break;
442 
443             case ISO_VD_PRIMARY:
444             case ISO_VD_SUPPLEMENTARY:
445                 idr = (struct iso_directory_record*) & (((struct iso_primary_descriptor*) & desc->data)->root_directory_record);
446                 joliet = JolietLevel(&desc->data);
447                 if (joliet) {
448                     QTextStream(&path) << "Joliet level " << joliet;
449                     if (c_j > 1) path += " (" + QString::number(c_j) + ')';
450                 } else {
451                     path = "ISO9660";
452                     if (c_i > 1) path += " (" + QString::number(c_i) + ')';
453                 }
454                 dirent = new KIsoDirectory(this, path, access | S_IFDIR,
455                                            buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, QString());
456                 root->addEntry(dirent);
457                 level = 0;
458                 mycallb(idr, this);
459                 if (joliet) c_j++; else c_i++;
460                 break;
461             }
462             desc = desc->next;
463         }
464         free(desc);
465     }
466     device()->close();
467     return true;
468 }
469 
closeArchive()470 bool KIso::closeArchive()
471 {
472     KRFUNC;
473     d->dirList.clear();
474     return true;
475 }
476 
writeDir(const QString &,const QString &,const QString &,mode_t,time_t,time_t,time_t)477 bool KIso::writeDir(const QString&, const QString&, const QString&, mode_t, time_t, time_t, time_t)
478 {
479     return false;
480 }
481 
prepareWriting(const QString &,const QString &,const QString &,qint64,mode_t,time_t,time_t,time_t)482 bool KIso::prepareWriting(const QString&, const QString&, const QString&, qint64, mode_t, time_t, time_t, time_t)
483 {
484     return false;
485 }
486 
finishWriting(qint64)487 bool KIso::finishWriting(qint64)
488 {
489     return false;
490 }
491 
writeSymLink(const QString &,const QString &,const QString &,const QString &,mode_t,time_t,time_t,time_t)492 bool KIso::writeSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, time_t, time_t, time_t)
493 {
494     return false;
495 }
496 
doWriteDir(const QString &,const QString &,const QString &,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)497 bool KIso::doWriteDir(const QString&, const QString&, const QString&, mode_t, const QDateTime&, const QDateTime &, const QDateTime &)
498 {
499     return false;
500 }
501 
doWriteSymLink(const QString &,const QString &,const QString &,const QString &,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)502 bool KIso::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime&, const QDateTime&, const QDateTime&)
503 {
504     return false;
505 }
506 
doPrepareWriting(const QString &,const QString &,const QString &,qint64,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)507 bool KIso::doPrepareWriting(const QString& , const QString& , const QString& , qint64, mode_t, const QDateTime&, const QDateTime&, const QDateTime&)
508 {
509     return false;
510 }
511 
doFinishWriting(qint64)512 bool KIso::doFinishWriting(qint64)
513 {
514     return false;
515 }
516 
virtual_hook(int id,void * data)517 void KIso::virtual_hook(int id, void* data)
518 {
519     KArchive::virtual_hook(id, data);
520 }
521 
522