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
20static constexpr int kMaxQByteArraySize = std::numeric_limits<int>::max() - 32;
21
22////////////////////////////////////////////////////////////////////////
23/////////////////////////// KAr ///////////////////////////////////////
24////////////////////////////////////////////////////////////////////////
25
26class Q_DECL_HIDDEN KAr::KArPrivate
27{
28public:
29 KArPrivate()
30 {
31 }
32};
33
34KAr::KAr(const QString &filename)
35 : KArchive(filename)
36 , d(new KArPrivate)
37{
38}
39
40KAr::KAr(QIODevice *dev)
41 : KArchive(dev)
42 , d(new KArPrivate)
43{
44}
45
46KAr::~KAr()
47{
48 if (isOpen()) {
49 close();
50 }
51 delete d;
52}
53
54bool KAr::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
55{
56 setErrorString(tr(sourceText: "Cannot write to AR file"));
57 qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KAr";
58 return false;
59}
60
61bool KAr::doFinishWriting(qint64)
62{
63 setErrorString(tr(sourceText: "Cannot write to AR file"));
64 qCWarning(KArchiveLog) << "doFinishWriting not implemented for KAr";
65 return false;
66}
67
68bool KAr::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
69{
70 setErrorString(tr(sourceText: "Cannot write to AR file"));
71 qCWarning(KArchiveLog) << "doWriteDir not implemented for KAr";
72 return false;
73}
74
75bool KAr::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
76{
77 setErrorString(tr(sourceText: "Cannot write to AR file"));
78 qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KAr";
79 return false;
80}
81
82bool 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(sourceText: "Unsupported mode %1").arg(a: mode));
91 return false;
92 }
93
94 QIODevice *dev = device();
95 if (!dev) {
96 return false;
97 }
98
99 QByteArray magic = dev->read(maxlen: 7);
100 if (magic != "!<arch>") {
101 setErrorString(tr(sourceText: "Invalid main magic"));
102 return false;
103 }
104
105 QByteArray ar_longnames;
106 while (!dev->atEnd()) {
107 QByteArray ar_header;
108 ar_header.resize(size: 60);
109
110 dev->seek(pos: dev->pos() + (2 - (dev->pos() % 2)) % 2); // Ar headers are padded to byte boundary
111
112 if (dev->read(data: ar_header.data(), maxlen: 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(bv: "`\n")) { // Check header magic // krazy:exclude=strings
118 setErrorString(tr(sourceText: "Invalid magic"));
119 return false;
120 }
121
122 QByteArray name = ar_header.mid(index: 0, len: 16); // Process header
123 const int date = ar_header.mid(index: 16, len: 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(index: 40, len: 8).trimmed().toInt(ok: nullptr, base: 8);
127 const qint64 size = ar_header.mid(index: 48, len: 10).trimmed().toInt();
128 if (size < 0 || size > kMaxQByteArraySize) {
129 setErrorString(tr(sourceText: "Invalid size"));
130 return false;
131 }
132
133 bool skip_entry = false; // Deal with special entries
134 if (name.mid(index: 0, len: 1) == "/") {
135 if (name.mid(index: 1, len: 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(data: ar_longnames.data(), maxlen: size);
139 skip_entry = true;
140 qCDebug(KArchiveLog) << "Read in longnames entry";
141 } else if (name.mid(index: 1, len: 1) == " ") { // Symbol table entry
142 qCDebug(KArchiveLog) << "Skipped symbol entry";
143 dev->seek(pos: dev->pos() + size);
144 skip_entry = true;
145 } else { // Longfilename, look it up in the table
146 const int ar_longnamesIndex = name.mid(index: 1, len: 15).trimmed().toInt();
147 qCDebug(KArchiveLog) << "Longfilename #" << ar_longnamesIndex;
148 if (ar_longnames.isEmpty()) {
149 setErrorString(tr(sourceText: "Invalid longfilename reference"));
150 return false;
151 }
152 if (ar_longnamesIndex < 0 || ar_longnamesIndex >= ar_longnames.size()) {
153 setErrorString(tr(sourceText: "Invalid longfilename position reference"));
154 return false;
155 }
156 name = QByteArray(ar_longnames.constData() + ar_longnamesIndex);
157 name.truncate(pos: name.indexOf(c: '/'));
158 }
159 }
160 if (skip_entry) {
161 continue;
162 }
163
164 // Process filename
165 name = name.trimmed();
166 name.replace(before: '/', after: QByteArray());
167 qCDebug(KArchiveLog) << "Filename: " << name << " Size: " << size;
168
169 KArchiveEntry *entry = new KArchiveFile(this,
170 QString::fromLocal8Bit(ba: name.constData()),
171 mode,
172 KArchivePrivate::time_tToDateTime(time_t: 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(pos: dev->pos() + size); // Skip contents
181 }
182
183 return true;
184}
185
186bool KAr::closeArchive()
187{
188 // Close the archive
189 return true;
190}
191
192void KAr::virtual_hook(int id, void *data)
193{
194 KArchive::virtual_hook(id, data);
195}
196

source code of karchive/src/kar.cpp