1 /* This file is part of the KDE libraries
2    SPDX-FileCopyrightText: 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
3 
4    SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "kar.h"
8 #include "karchive_p.h"
9 #include "loggingcategory.h"
10 
11 #include <QDebug>
12 #include <QFile>
13 
14 #include <limits>
15 
16 #include "kcompressiondevice.h"
17 //#include "klimitediodevice_p.h"
18 
19 // As documented in QByteArray
20 static constexpr int kMaxQByteArraySize = std::numeric_limits<int>::max() - 32;
21 
22 ////////////////////////////////////////////////////////////////////////
23 /////////////////////////// KAr ///////////////////////////////////////
24 ////////////////////////////////////////////////////////////////////////
25 
26 class Q_DECL_HIDDEN KAr::KArPrivate
27 {
28 public:
KArPrivate()29     KArPrivate()
30     {
31     }
32 };
33 
KAr(const QString & filename)34 KAr::KAr(const QString &filename)
35     : KArchive(filename)
36     , d(new KArPrivate)
37 {
38 }
39 
KAr(QIODevice * dev)40 KAr::KAr(QIODevice *dev)
41     : KArchive(dev)
42     , d(new KArPrivate)
43 {
44 }
45 
~KAr()46 KAr::~KAr()
47 {
48     if (isOpen()) {
49         close();
50     }
51     delete d;
52 }
53 
doPrepareWriting(const QString &,const QString &,const QString &,qint64,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)54 bool KAr::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
55 {
56     setErrorString(tr("Cannot write to AR file"));
57     qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KAr";
58     return false;
59 }
60 
doFinishWriting(qint64)61 bool KAr::doFinishWriting(qint64)
62 {
63     setErrorString(tr("Cannot write to AR file"));
64     qCWarning(KArchiveLog) << "doFinishWriting not implemented for KAr";
65     return false;
66 }
67 
doWriteDir(const QString &,const QString &,const QString &,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)68 bool KAr::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
69 {
70     setErrorString(tr("Cannot write to AR file"));
71     qCWarning(KArchiveLog) << "doWriteDir not implemented for KAr";
72     return false;
73 }
74 
doWriteSymLink(const QString &,const QString &,const QString &,const QString &,mode_t,const QDateTime &,const QDateTime &,const QDateTime &)75 bool KAr::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
76 {
77     setErrorString(tr("Cannot write to AR file"));
78     qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KAr";
79     return false;
80 }
81 
openArchive(QIODevice::OpenMode mode)82 bool KAr::openArchive(QIODevice::OpenMode mode)
83 {
84     // Open archive
85 
86     if (mode == QIODevice::WriteOnly) {
87         return true;
88     }
89     if (mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite) {
90         setErrorString(tr("Unsupported mode %1").arg(mode));
91         return false;
92     }
93 
94     QIODevice *dev = device();
95     if (!dev) {
96         return false;
97     }
98 
99     QByteArray magic = dev->read(7);
100     if (magic != "!<arch>") {
101         setErrorString(tr("Invalid main magic"));
102         return false;
103     }
104 
105     QByteArray ar_longnames;
106     while (!dev->atEnd()) {
107         QByteArray ar_header;
108         ar_header.resize(60);
109 
110         dev->seek(dev->pos() + (2 - (dev->pos() % 2)) % 2); // Ar headers are padded to byte boundary
111 
112         if (dev->read(ar_header.data(), 60) != 60) { // Read ar header
113             qCWarning(KArchiveLog) << "Couldn't read header";
114             return true; // Probably EOF / trailing junk
115         }
116 
117         if (!ar_header.endsWith("`\n")) { // Check header magic // krazy:exclude=strings
118             setErrorString(tr("Invalid magic"));
119             return false;
120         }
121 
122         QByteArray name = ar_header.mid(0, 16); // Process header
123         const int date = ar_header.mid(16, 12).trimmed().toInt();
124         // const int uid = ar_header.mid( 28, 6 ).trimmed().toInt();
125         // const int gid = ar_header.mid( 34, 6 ).trimmed().toInt();
126         const int mode = ar_header.mid(40, 8).trimmed().toInt(nullptr, 8);
127         const qint64 size = ar_header.mid(48, 10).trimmed().toInt();
128         if (size < 0 || size > kMaxQByteArraySize) {
129             setErrorString(tr("Invalid size"));
130             return false;
131         }
132 
133         bool skip_entry = false; // Deal with special entries
134         if (name.mid(0, 1) == "/") {
135             if (name.mid(1, 1) == "/") { // Longfilename table entry
136                 ar_longnames.resize(size);
137                 // Read the table. Note that the QByteArray will contain NUL characters after each entry.
138                 dev->read(ar_longnames.data(), size);
139                 skip_entry = true;
140                 qCDebug(KArchiveLog) << "Read in longnames entry";
141             } else if (name.mid(1, 1) == " ") { // Symbol table entry
142                 qCDebug(KArchiveLog) << "Skipped symbol entry";
143                 dev->seek(dev->pos() + size);
144                 skip_entry = true;
145             } else { // Longfilename, look it up in the table
146                 const int ar_longnamesIndex = name.mid(1, 15).trimmed().toInt();
147                 qCDebug(KArchiveLog) << "Longfilename #" << ar_longnamesIndex;
148                 if (ar_longnames.isEmpty()) {
149                     setErrorString(tr("Invalid longfilename reference"));
150                     return false;
151                 }
152                 if (ar_longnamesIndex < 0 || ar_longnamesIndex >= ar_longnames.size()) {
153                     setErrorString(tr("Invalid longfilename position reference"));
154                     return false;
155                 }
156                 name = QByteArray(ar_longnames.constData() + ar_longnamesIndex);
157                 name.truncate(name.indexOf('/'));
158             }
159         }
160         if (skip_entry) {
161             continue;
162         }
163 
164         // Process filename
165         name = name.trimmed();
166         name.replace('/', QByteArray());
167         qCDebug(KArchiveLog) << "Filename: " << name << " Size: " << size;
168 
169         KArchiveEntry *entry = new KArchiveFile(this,
170                                                 QString::fromLocal8Bit(name.constData()),
171                                                 mode,
172                                                 KArchivePrivate::time_tToDateTime(date),
173                                                 rootDir()->user(),
174                                                 rootDir()->group(),
175                                                 /*symlink*/ QString(),
176                                                 dev->pos(),
177                                                 size);
178         rootDir()->addEntry(entry); // Ar files don't support directories, so everything in root
179 
180         dev->seek(dev->pos() + size); // Skip contents
181     }
182 
183     return true;
184 }
185 
closeArchive()186 bool KAr::closeArchive()
187 {
188     // Close the archive
189     return true;
190 }
191 
virtual_hook(int id,void * data)192 void KAr::virtual_hook(int id, void *data)
193 {
194     KArchive::virtual_hook(id, data);
195 }
196