File indexing completed on 2024-04-14 03:50:27

0001 /* This file is part of the KDE libraries
0002    SPDX-FileCopyrightText: 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kar.h"
0008 #include "karchive_p.h"
0009 #include "loggingcategory.h"
0010 
0011 #include <QDebug>
0012 #include <QFile>
0013 
0014 #include <limits>
0015 
0016 #include "kcompressiondevice.h"
0017 //#include "klimitediodevice_p.h"
0018 
0019 // As documented in QByteArray
0020 static constexpr int kMaxQByteArraySize = std::numeric_limits<int>::max() - 32;
0021 
0022 ////////////////////////////////////////////////////////////////////////
0023 /////////////////////////// KAr ///////////////////////////////////////
0024 ////////////////////////////////////////////////////////////////////////
0025 
0026 class Q_DECL_HIDDEN KAr::KArPrivate
0027 {
0028 public:
0029     KArPrivate()
0030     {
0031     }
0032 };
0033 
0034 KAr::KAr(const QString &filename)
0035     : KArchive(filename)
0036     , d(new KArPrivate)
0037 {
0038 }
0039 
0040 KAr::KAr(QIODevice *dev)
0041     : KArchive(dev)
0042     , d(new KArPrivate)
0043 {
0044 }
0045 
0046 KAr::~KAr()
0047 {
0048     if (isOpen()) {
0049         close();
0050     }
0051     delete d;
0052 }
0053 
0054 bool KAr::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0055 {
0056     setErrorString(tr("Cannot write to AR file"));
0057     qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KAr";
0058     return false;
0059 }
0060 
0061 bool KAr::doFinishWriting(qint64)
0062 {
0063     setErrorString(tr("Cannot write to AR file"));
0064     qCWarning(KArchiveLog) << "doFinishWriting not implemented for KAr";
0065     return false;
0066 }
0067 
0068 bool KAr::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0069 {
0070     setErrorString(tr("Cannot write to AR file"));
0071     qCWarning(KArchiveLog) << "doWriteDir not implemented for KAr";
0072     return false;
0073 }
0074 
0075 bool KAr::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
0076 {
0077     setErrorString(tr("Cannot write to AR file"));
0078     qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KAr";
0079     return false;
0080 }
0081 
0082 bool KAr::openArchive(QIODevice::OpenMode mode)
0083 {
0084     // Open archive
0085 
0086     if (mode == QIODevice::WriteOnly) {
0087         return true;
0088     }
0089     if (mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite) {
0090         setErrorString(tr("Unsupported mode %1").arg(mode));
0091         return false;
0092     }
0093 
0094     QIODevice *dev = device();
0095     if (!dev) {
0096         return false;
0097     }
0098 
0099     QByteArray magic = dev->read(7);
0100     if (magic != "!<arch>") {
0101         setErrorString(tr("Invalid main magic"));
0102         return false;
0103     }
0104 
0105     QByteArray ar_longnames;
0106     while (!dev->atEnd()) {
0107         QByteArray ar_header;
0108         ar_header.resize(60);
0109 
0110         dev->seek(dev->pos() + (2 - (dev->pos() % 2)) % 2); // Ar headers are padded to byte boundary
0111 
0112         if (dev->read(ar_header.data(), 60) != 60) { // Read ar header
0113             qCWarning(KArchiveLog) << "Couldn't read header";
0114             return true; // Probably EOF / trailing junk
0115         }
0116 
0117         if (!ar_header.endsWith("`\n")) { // Check header magic // krazy:exclude=strings
0118             setErrorString(tr("Invalid magic"));
0119             return false;
0120         }
0121 
0122         QByteArray name = ar_header.mid(0, 16); // Process header
0123         const int date = ar_header.mid(16, 12).trimmed().toInt();
0124         // const int uid = ar_header.mid( 28, 6 ).trimmed().toInt();
0125         // const int gid = ar_header.mid( 34, 6 ).trimmed().toInt();
0126         const int mode = ar_header.mid(40, 8).trimmed().toInt(nullptr, 8);
0127         const qint64 size = ar_header.mid(48, 10).trimmed().toInt();
0128         if (size < 0 || size > kMaxQByteArraySize) {
0129             setErrorString(tr("Invalid size"));
0130             return false;
0131         }
0132 
0133         bool skip_entry = false; // Deal with special entries
0134         if (name.mid(0, 1) == "/") {
0135             if (name.mid(1, 1) == "/") { // Longfilename table entry
0136                 ar_longnames.resize(size);
0137                 // Read the table. Note that the QByteArray will contain NUL characters after each entry.
0138                 dev->read(ar_longnames.data(), size);
0139                 skip_entry = true;
0140                 qCDebug(KArchiveLog) << "Read in longnames entry";
0141             } else if (name.mid(1, 1) == " ") { // Symbol table entry
0142                 qCDebug(KArchiveLog) << "Skipped symbol entry";
0143                 dev->seek(dev->pos() + size);
0144                 skip_entry = true;
0145             } else { // Longfilename, look it up in the table
0146                 const int ar_longnamesIndex = name.mid(1, 15).trimmed().toInt();
0147                 qCDebug(KArchiveLog) << "Longfilename #" << ar_longnamesIndex;
0148                 if (ar_longnames.isEmpty()) {
0149                     setErrorString(tr("Invalid longfilename reference"));
0150                     return false;
0151                 }
0152                 if (ar_longnamesIndex < 0 || ar_longnamesIndex >= ar_longnames.size()) {
0153                     setErrorString(tr("Invalid longfilename position reference"));
0154                     return false;
0155                 }
0156                 name = QByteArray(ar_longnames.constData() + ar_longnamesIndex);
0157                 name.truncate(name.indexOf('/'));
0158             }
0159         }
0160         if (skip_entry) {
0161             continue;
0162         }
0163 
0164         // Process filename
0165         name = name.trimmed();
0166         name.replace('/', QByteArray());
0167         qCDebug(KArchiveLog) << "Filename: " << name << " Size: " << size;
0168 
0169         KArchiveEntry *entry = new KArchiveFile(this,
0170                                                 QString::fromLocal8Bit(name.constData()),
0171                                                 mode,
0172                                                 KArchivePrivate::time_tToDateTime(date),
0173                                                 rootDir()->user(),
0174                                                 rootDir()->group(),
0175                                                 /*symlink*/ QString(),
0176                                                 dev->pos(),
0177                                                 size);
0178         rootDir()->addEntry(entry); // Ar files don't support directories, so everything in root
0179 
0180         dev->seek(dev->pos() + size); // Skip contents
0181     }
0182 
0183     return true;
0184 }
0185 
0186 bool KAr::closeArchive()
0187 {
0188     // Close the archive
0189     return true;
0190 }
0191 
0192 void KAr::virtual_hook(int id, void *data)
0193 {
0194     KArchive::virtual_hook(id, data);
0195 }