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