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 "iso.h"
26 
27 #include <zlib.h>
28 
29 // QtCore
30 #include <QByteArray>
31 #include <QDebug>
32 #include <QDir>
33 #include <QFile>
34 #include <QMimeDatabase>
35 #include <QMimeType>
36 #include <qplatformdefs.h>
37 
38 #include "libisofs/iso_fs.h"
39 #include "kiso.h"
40 #include "kisofile.h"
41 #include "kisodirectory.h"
42 #include "../krusader/compat.h"
43 
44 using namespace KIO;
45 extern "C"
46 {
47 
kdemain(int argc,char ** argv)48     int Q_DECL_EXPORT kdemain(int argc, char **argv) {
49         //qDebug()   << "Starting " << getpid() << endl;
50 
51         if (argc != 4) {
52             fprintf(stderr, "Usage: kio_iso protocol domain-socket1 domain-socket2\n");
53             exit(-1);
54         }
55 
56         kio_isoProtocol slave(argv[2], argv[3]);
57         slave.dispatchLoop();
58 
59         //qDebug()   << "Done" << endl;
60         return 0;
61     }
62 
63 } // extern "C"
64 
65 typedef struct {
66     char magic[8];
67     char uncompressed_len[4];
68     unsigned char header_size;
69     unsigned char block_size;
70     char reserved[2];   /* Reserved for future use, MBZ */
71 } compressed_file_header;
72 
73 static const unsigned char zisofs_magic[8] = {
74     0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07
75 };
76 
77 
kio_isoProtocol(const QByteArray & pool,const QByteArray & app)78 kio_isoProtocol::kio_isoProtocol(const QByteArray &pool, const QByteArray &app) : SlaveBase("iso", pool, app)
79 {
80     //qDebug() << "kio_isoProtocol::kio_isoProtocol" << endl;
81     m_isoFile = 0L;
82 }
83 
~kio_isoProtocol()84 kio_isoProtocol::~kio_isoProtocol()
85 {
86     delete m_isoFile;
87 }
88 
checkNewFile(QString fullPath,QString & path,int startsec)89 bool kio_isoProtocol::checkNewFile(QString fullPath, QString & path, int startsec)
90 {
91     //qDebug()   << "kio_isoProtocol::checkNewFile " << fullPath << " startsec: " <<
92     //startsec << endl;
93 
94     // Are we already looking at that file ?
95     if (m_isoFile && startsec == m_isoFile->startSec() &&
96             m_isoFile->fileName() == fullPath.left(m_isoFile->fileName().length())) {
97         // Has it changed ?
98         QT_STATBUF statbuf;
99         if (QT_STAT(QFile::encodeName(m_isoFile->fileName()), &statbuf) == 0) {
100             if (m_mtime == statbuf.st_mtime) {
101                 path = fullPath.mid(m_isoFile->fileName().length());
102                 //qDebug()   << "kio_isoProtocol::checkNewFile returning " << path << endl;
103                 if(path.endsWith(DIR_SEPARATOR_CHAR)) {
104                     path.chop(1);
105                 }
106                 if(path.isEmpty()) {
107                     path = DIR_SEPARATOR_CHAR;
108                 }
109                 return true;
110             }
111         }
112     }
113     //qDebug() << "Need to open a new file" << endl;
114 
115     // Close previous file
116     if (m_isoFile) {
117         m_isoFile->close();
118         delete m_isoFile;
119         m_isoFile = 0L;
120     }
121 
122     // Find where the iso file is in the full path
123     int pos = 0;
124     QString isoFile;
125     path.clear();
126 
127     int len = fullPath.length();
128     if (len != 0 && fullPath[ len - 1 ] != DIR_SEPARATOR_CHAR)
129         fullPath += DIR_SEPARATOR_CHAR;
130 
131     //qDebug()   << "the full path is " << fullPath << endl;
132     while ((pos = fullPath.indexOf(DIR_SEPARATOR_CHAR, pos + 1)) != -1) {
133         QString tryPath = fullPath.left(pos);
134         //qDebug()   << fullPath << "  trying " << tryPath << endl;
135 
136         QT_STATBUF statbuf;
137         if (QT_LSTAT(QFile::encodeName(tryPath), &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) {
138             bool isFile = true;
139             if (S_ISLNK(statbuf.st_mode)) {
140                 char symDest[256];
141                 memset(symDest, 0, 256);
142                 int endOfName = readlink(QFile::encodeName(tryPath), symDest, 256);
143                 if (endOfName != -1) {
144                     if (QDir(QString::fromLocal8Bit(symDest)).exists())
145                         isFile = false;
146                 }
147             }
148 
149             if (isFile) {
150                 isoFile = tryPath;
151                 m_mtime = statbuf.st_mtime;
152                 m_mode = statbuf.st_mode;
153                 path = fullPath.mid(pos + 1);
154                 //qDebug()   << "fullPath=" << fullPath << " path=" << path << endl;
155                 if(path.endsWith(DIR_SEPARATOR_CHAR)) {
156                     path.chop(1);
157                 }
158                 if(path.isEmpty()) {
159                     path = DIR_SEPARATOR_CHAR;
160                 }
161                 //qDebug()   << "Found. isoFile=" << isoFile << " path=" << path << endl;
162                 break;
163             }
164         }
165     }
166     if (isoFile.isEmpty()) {
167         //qDebug()   << "kio_isoProtocol::checkNewFile: not found" << endl;
168         return false;
169     }
170 
171     // Open new file
172     //qDebug() << "Opening KIso on " << isoFile << endl;
173     m_isoFile = new KIso(isoFile);
174     m_isoFile->setStartSec(startsec);
175     if (!m_isoFile->open(QIODevice::ReadOnly)) {
176         //qDebug()   << "Opening " << isoFile << " failed." << endl;
177         delete m_isoFile;
178         m_isoFile = 0L;
179         return false;
180     }
181 
182     return true;
183 }
184 
185 
createUDSEntry(const KArchiveEntry * isoEntry,UDSEntry & entry)186 void kio_isoProtocol::createUDSEntry(const KArchiveEntry * isoEntry, UDSEntry & entry)
187 {
188     entry.clear();
189     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_NAME, isoEntry->name());
190     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_FILE_TYPE, isoEntry->permissions() & S_IFMT);   // keep file type only
191     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_ACCESS, isoEntry->permissions() & 07777);   // keep permissions only
192 
193     if (isoEntry->isFile()) {
194         long long si = ((KIsoFile *)isoEntry)->realsize();
195         if (!si) si = ((KIsoFile *)isoEntry)->size();
196         entry.UDS_ENTRY_INSERT(UDSEntry::UDS_SIZE, si);
197     } else {
198         entry.UDS_ENTRY_INSERT(UDSEntry::UDS_SIZE, 0L);
199     }
200 
201     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_USER, isoEntry->user());
202     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_GROUP, isoEntry->group());
203     entry.UDS_ENTRY_INSERT((uint)UDSEntry::UDS_MODIFICATION_TIME, isoEntry->date().toTime_t());
204     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_ACCESS_TIME,
205                  isoEntry->isFile() ? ((KIsoFile *)isoEntry)->adate() :
206                  ((KIsoDirectory *)isoEntry)->adate());
207 
208     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_CREATION_TIME,
209                  isoEntry->isFile() ? ((KIsoFile *)isoEntry)->cdate() :
210                  ((KIsoDirectory *)isoEntry)->cdate());
211 
212     entry.UDS_ENTRY_INSERT(UDSEntry::UDS_LINK_DEST, isoEntry->symLinkTarget());
213 }
214 
listDir(const QUrl & url)215 void kio_isoProtocol::listDir(const QUrl &url)
216 {
217     //qDebug() << "kio_isoProtocol::listDir " << url.url() << endl;
218 
219     QString path;
220     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
221         QByteArray _path(QFile::encodeName(getPath(url)));
222         //qDebug()  << "Checking (stat) on " << _path << endl;
223         QT_STATBUF buff;
224         if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) {
225             error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
226             return;
227         }
228         // It's a real dir -> redirect
229         QUrl redir;
230         redir.setPath(getPath(url));
231         if (url.hasFragment()) redir.setFragment(url.fragment(QUrl::FullyDecoded));
232         //qDebug()  << "Ok, redirection to " << redir.url() << endl;
233         redir.setScheme("file");
234         redirection(redir);
235         finished();
236         // And let go of the iso file - for people who want to unmount a cdrom after that
237         delete m_isoFile;
238         m_isoFile = 0L;
239         return;
240     }
241 
242     if (path.isEmpty()) {
243         QUrl redir(QStringLiteral("iso:/"));
244         //qDebug() << "url.path()==" << getPath(url) << endl;
245         if (url.hasFragment()) redir.setFragment(url.fragment(QUrl::FullyDecoded));
246         redir.setPath(getPath(url) + QString::fromLatin1(DIR_SEPARATOR));
247         //qDebug() << "kio_isoProtocol::listDir: redirection " << redir.url() << endl;
248         redir.setScheme("file");
249         redirection(redir);
250         finished();
251         return;
252     }
253 
254     //qDebug()  << "checkNewFile done" << endl;
255     const KArchiveDirectory* root = m_isoFile->directory();
256     const KArchiveDirectory* dir;
257     if (!path.isEmpty() && path != DIR_SEPARATOR) {
258         //qDebug()   << QString("Looking for entry %1").arg(path) << endl;
259         const KArchiveEntry* e = root->entry(path);
260         if (!e) {
261             error(KIO::ERR_DOES_NOT_EXIST, path);
262             return;
263         }
264         if (! e->isDirectory()) {
265             error(KIO::ERR_IS_FILE, path);
266             return;
267         }
268         dir = (KArchiveDirectory*)e;
269     } else {
270         dir = root;
271     }
272 
273     QStringList l = dir->entries();
274     totalSize(l.count());
275 
276     UDSEntry entry;
277     QStringList::Iterator it = l.begin();
278     for (; it != l.end(); ++it) {
279         //qDebug()   << (*it) << endl;
280         const KArchiveEntry* isoEntry = dir->entry((*it));
281 
282         createUDSEntry(isoEntry, entry);
283 
284         listEntry(entry);
285     }
286 
287     finished();
288     //qDebug()  << "kio_isoProtocol::listDir done" << endl;
289 }
290 
stat(const QUrl & url)291 void kio_isoProtocol::stat(const QUrl &url)
292 {
293     QString path;
294     UDSEntry entry;
295 
296     //qDebug() << "kio_isoProtocol::stat " << url.url() << endl;
297     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
298         // We may be looking at a real directory - this happens
299         // when pressing up after being in the root of an archive
300         QByteArray _path(QFile::encodeName(getPath(url)));
301         //qDebug()  << "kio_isoProtocol::stat (stat) on " << _path << endl;
302         QT_STATBUF buff;
303         if (QT_STAT(_path.data(), &buff) == -1 || !S_ISDIR(buff.st_mode)) {
304             //qDebug() << "isdir=" << S_ISDIR(buff.st_mode) << "  errno=" << strerror(errno) << endl;
305             error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
306             return;
307         }
308         // Real directory. Return just enough information for KRun to work
309         entry.UDS_ENTRY_INSERT(UDSEntry::UDS_NAME, url.fileName());
310         //qDebug()  << "kio_isoProtocol::stat returning name=" << url.fileName() << endl;
311 
312         entry.UDS_ENTRY_INSERT(UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
313 
314         statEntry(entry);
315 
316         finished();
317 
318         // And let go of the iso file - for people who want to unmount a cdrom after that
319         delete m_isoFile;
320         m_isoFile = 0L;
321         return;
322     }
323 
324     const KArchiveDirectory* root = m_isoFile->directory();
325     const KArchiveEntry* isoEntry;
326     if (path.isEmpty()) {
327         path = QString::fromLatin1(DIR_SEPARATOR);
328         isoEntry = root;
329     } else {
330         isoEntry = root->entry(path);
331     }
332     if (!isoEntry) {
333         error(KIO::ERR_DOES_NOT_EXIST, path);
334         return;
335     }
336     createUDSEntry(isoEntry, entry);
337     statEntry(entry);
338     finished();
339 }
340 
getFile(const KIsoFile * isoFileEntry,const QString & path)341 void kio_isoProtocol::getFile(const KIsoFile *isoFileEntry, const QString &path)
342 {
343     unsigned long long size, pos = 0;
344     bool mime = false, zlib = false;
345     QByteArray fileData, pointer_block, inbuf, outbuf;
346     char *pptr = 0;
347     compressed_file_header *hdr;
348     int block_shift;
349     unsigned long nblocks;
350     unsigned long fullsize = 0, block_size = 0, block_size2 = 0;
351     size_t ptrblock_bytes;
352     unsigned long cstart, cend, csize;
353     uLong bytes;
354 
355     size = isoFileEntry->realsize();
356     if (size >= sizeof(compressed_file_header)) zlib = true;
357     if (!size) size = isoFileEntry->size();
358     totalSize(size);
359     if (!size) mimeType("application/x-zerosize");
360 
361     if (size && !m_isoFile->device()->isOpen()) {
362         m_isoFile->device()->open(QIODevice::ReadOnly);
363 
364         // seek(0) ensures integrity with the QIODevice's built-in buffer
365         // see bug #372023 for details
366         m_isoFile->device()->seek(0);
367     }
368 
369     if (zlib) {
370         fileData = isoFileEntry->dataAt(0, sizeof(compressed_file_header));
371         if (fileData.size() == sizeof(compressed_file_header) &&
372                 !memcmp(fileData.data(), zisofs_magic, sizeof(zisofs_magic))) {
373 
374             hdr = (compressed_file_header*) fileData.data();
375             block_shift = hdr->block_size;
376             block_size  = 1UL << block_shift;
377             block_size2 = block_size << 1;
378             fullsize    = isonum_731(hdr->uncompressed_len);
379             nblocks = (fullsize + block_size - 1) >> block_shift;
380             ptrblock_bytes = (nblocks + 1) * 4;
381             pointer_block = isoFileEntry->dataAt(hdr->header_size << 2, ptrblock_bytes);
382             if ((unsigned long)pointer_block.size() == ptrblock_bytes) {
383                 inbuf.resize(block_size2);
384                 if (inbuf.size()) {
385                     outbuf.resize(block_size);
386 
387                     if (outbuf.size())
388                         pptr = pointer_block.data();
389                     else {
390                         error(KIO::ERR_COULD_NOT_READ, path);
391                         return;
392                     }
393                 } else {
394                     error(KIO::ERR_COULD_NOT_READ, path);
395                     return;
396                 }
397             } else {
398                 error(KIO::ERR_COULD_NOT_READ, path);
399                 return;
400             }
401         } else {
402             zlib = false;
403         }
404     }
405 
406     while (pos < size) {
407         if (zlib) {
408             cstart = isonum_731(pptr);
409             pptr += 4;
410             cend   = isonum_731(pptr);
411 
412             csize = cend - cstart;
413 
414             if (csize == 0) {
415                 outbuf.fill(0, -1);
416             } else {
417                 if (csize > block_size2) {
418                     //err = EX_DATAERR;
419                     break;
420                 }
421 
422                 inbuf = isoFileEntry->dataAt(cstart, csize);
423                 if ((unsigned long)inbuf.size() != csize) {
424                     break;
425                 }
426 
427                 bytes = block_size; // Max output buffer size
428                 if ((uncompress((Bytef*) outbuf.data(), &bytes, (Bytef*) inbuf.data(), csize)) != Z_OK) {
429                     break;
430                 }
431             }
432 
433             if (((fullsize > block_size) && (bytes != block_size))
434                     || ((fullsize <= block_size) && (bytes < fullsize))) {
435 
436                 break;
437             }
438 
439             if (bytes > fullsize)
440                 bytes = fullsize;
441             fileData = outbuf;
442             fileData.resize(bytes);
443             fullsize -= bytes;
444         } else {
445             fileData = isoFileEntry->dataAt(pos, 65536);
446             if (fileData.size() == 0) break;
447         }
448         if (!mime) {
449             QMimeDatabase db;
450             QMimeType mt = db.mimeTypeForFileNameAndData(path, fileData);
451             if (mt.isValid()) {
452                 //qDebug() << "Emitting mimetype " << mt.name() << endl;
453                 mimeType(mt.name());
454                 mime = true;
455             }
456         }
457         data(fileData);
458         pos += fileData.size();
459         processedSize(pos);
460     }
461 
462     if (pos != size) {
463         error(KIO::ERR_COULD_NOT_READ, path);
464         return;
465     }
466 
467     fileData.resize(0);
468     data(fileData);
469     processedSize(pos);
470     finished();
471 
472 }
473 
get(const QUrl & url)474 void kio_isoProtocol::get(const QUrl &url)
475 {
476     //qDebug()  << "kio_isoProtocol::get" << url.url() << endl;
477 
478     QString path;
479     if (!checkNewFile(getPath(url), path, url.hasFragment() ? url.fragment(QUrl::FullyDecoded).toInt() : -1)) {
480         error(KIO::ERR_DOES_NOT_EXIST, getPath(url));
481         return;
482     }
483 
484     const KArchiveDirectory* root = m_isoFile->directory();
485     const KArchiveEntry* isoEntry = root->entry(path);
486 
487     if (!isoEntry) {
488         error(KIO::ERR_DOES_NOT_EXIST, path);
489         return;
490     }
491     if (isoEntry->isDirectory()) {
492         error(KIO::ERR_IS_DIRECTORY, path);
493         return;
494     }
495 
496     const KIsoFile* isoFileEntry = static_cast<const KIsoFile *>(isoEntry);
497     if (!isoEntry->symLinkTarget().isEmpty()) {
498         //qDebug() << "Redirection to " << isoEntry->symLinkTarget() << endl;
499         QUrl realURL = QUrl(url).resolved(QUrl(isoEntry->symLinkTarget()));
500         //qDebug() << "realURL= " << realURL.url() << endl;
501         realURL.setScheme("file");
502         redirection(realURL);
503         finished();
504         return;
505     }
506     getFile(isoFileEntry, path);
507     if (m_isoFile->device()->isOpen()) m_isoFile->device()->close();
508 }
509 
getPath(const QUrl & url)510 QString kio_isoProtocol::getPath(const QUrl &url)
511 {
512     QString path = url.path();
513     REPLACE_DIR_SEP2(path);
514 
515 #ifdef Q_WS_WIN
516     if (path.startsWith(DIR_SEPARATOR)) {
517         int p = 1;
518         while (p < path.length() && path[ p ] == DIR_SEPARATOR_CHAR)
519             p++;
520         /* /C:/Folder */
521         if (p + 2 <= path.length() && path[ p ].isLetter() && path[ p + 1 ] == ':') {
522             path = path.mid(p);
523         }
524     }
525 #endif
526     return path;
527 }
528