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

source code of karchive/src/kar.cpp