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: |
29 | KArPrivate() |
30 | { |
31 | } |
32 | }; |
33 | |
34 | KAr::KAr(const QString &filename) |
35 | : KArchive(filename) |
36 | , d(new KArPrivate) |
37 | { |
38 | } |
39 | |
40 | KAr::KAr(QIODevice *dev) |
41 | : KArchive(dev) |
42 | , d(new KArPrivate) |
43 | { |
44 | } |
45 | |
46 | KAr::~KAr() |
47 | { |
48 | if (isOpen()) { |
49 | close(); |
50 | } |
51 | delete d; |
52 | } |
53 | |
54 | bool 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 | |
61 | bool 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 | |
68 | bool 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 | |
75 | bool 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 | |
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(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 ; |
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 | |
186 | bool KAr::closeArchive() |
187 | { |
188 | // Close the archive |
189 | return true; |
190 | } |
191 | |
192 | void KAr::virtual_hook(int id, void *data) |
193 | { |
194 | KArchive::virtual_hook(id, data); |
195 | } |
196 | |