Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmimemagicrule.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#define QT_NO_CAST_FROM_ASCII
6
8
10#include <QtCore/QList>
11#include <QtCore/QDebug>
12#include <qendian.h>
13
14#include <private/qoffsetstringarray_p.h>
15#include <private/qtools_p.h>
16
18
19using namespace Qt::StringLiterals;
20using namespace QtMiscUtils;
21
22// in the same order as Type!
23static constexpr auto magicRuleTypes = qOffsetStringArray(
24 "invalid",
25 "string",
26 "host16",
27 "host32",
28 "big16",
29 "big32",
30 "little16",
31 "little32",
32 "byte"
33);
34
35QMimeMagicRule::Type QMimeMagicRule::type(const QByteArray &theTypeName)
36{
37 for (int i = String; i <= Byte; ++i) {
38 if (theTypeName == magicRuleTypes.viewAt(i))
39 return Type(i);
40 }
41 return Invalid;
42}
43
44QByteArray QMimeMagicRule::typeName(QMimeMagicRule::Type theType)
45{
46 return magicRuleTypes.at(theType);
47}
48
49bool QMimeMagicRule::operator==(const QMimeMagicRule &other) const
50{
51 return m_type == other.m_type &&
52 m_value == other.m_value &&
53 m_startPos == other.m_startPos &&
54 m_endPos == other.m_endPos &&
55 m_mask == other.m_mask &&
56 m_pattern == other.m_pattern &&
57 m_number == other.m_number &&
58 m_numberMask == other.m_numberMask &&
59 m_matchFunction == other.m_matchFunction;
60}
61
62// Used by both providers
63bool QMimeMagicRule::matchSubstring(const char *dataPtr, qsizetype dataSize, quint64 rangeStart,
64 quint64 rangeLength, quint64 valueLength, const char *valueData,
65 const char *mask)
66{
67 // The range and the value length can come straight out of the binary mime.cache, so
68 // nothing can be assumed about them: rangeStart can point past the data, and the value
69 // read at the last position of the range has to stay inside it, too.
70 const quint64 size = quint64(dataSize);
71 if (rangeStart >= size || valueLength == 0 || rangeLength == 0
72 || valueLength > size - rangeStart)
73 return false;
74 // Clamp the range to the data that is actually there, so that rangeStart + rangeLength
75 // neither overflows nor points past the end.
76 rangeLength = qMin(rangeLength, size - rangeStart);
77
78 // Size of searched data.
79 // Example: value="ABC", rangeLength=3 -> we need 3+3-1=5 bytes (ABCxx,xABCx,xxABC would match)
80 const quint64 dataNeeded = qMin(rangeLength + valueLength - 1, size - rangeStart);
81
82 if (!mask) {
83 // callgrind says QByteArray::indexOf is much slower, since our strings are typically too
84 // short for be worth Boyer-Moore matching (1 to 71 bytes, 11 bytes on average).
85 bool found = false;
86 for (quint64 i = rangeStart; i < rangeStart + rangeLength; ++i) {
87 if (i + valueLength > size)
88 break;
89
90 if (memcmp(valueData, dataPtr + i, size_t(valueLength)) == 0) {
91 found = true;
92 break;
93 }
94 }
95 if (!found)
96 return false;
97 } else {
98 bool found = false;
99 const char *readDataBase = dataPtr + rangeStart;
100 // Example (continued from above):
101 // deviceSize is 4, so dataNeeded was max'ed to 4.
102 // maxStartPos = 4 - 3 + 1 = 2, and indeed
103 // we need to check for a match a positions 0 and 1 (ABCx and xABC).
104 const quint64 maxStartPos = dataNeeded - valueLength + 1;
105 for (quint64 i = 0; i < maxStartPos; ++i) {
106 const char *d = readDataBase + i;
107 bool valid = true;
108 for (quint64 idx = 0; idx < valueLength; ++idx) {
109 if (((*d++) & mask[idx]) != (valueData[idx] & mask[idx])) {
110 valid = false;
111 break;
112 }
113 }
114 if (valid) {
115 found = true;
116 break;
117 }
118 }
119 if (!found)
120 return false;
121 }
122 //qDebug() << "Found" << value << "in" << searchedData;
123 return true;
124}
125
126bool QMimeMagicRule::matchString(const QByteArray &data) const
127{
128 const int rangeLength = m_endPos - m_startPos + 1;
129 return QMimeMagicRule::matchSubstring(data.constData(), data.size(), m_startPos, rangeLength, m_pattern.size(), m_pattern.constData(), m_mask.constData());
130}
131
132template <typename T>
133bool QMimeMagicRule::matchNumber(const QByteArray &data) const
134{
135 const T value(m_number);
136 const T mask(m_numberMask);
137
138 //qDebug() << "matchNumber" << "0x" << QString::number(m_number, 16) << "size" << sizeof(T);
139 //qDebug() << "mask" << QString::number(m_numberMask, 16);
140
141 const char *p = data.constData() + m_startPos;
142 const char *e = data.constData() + qMin(data.size() - int(sizeof(T)), m_endPos);
143 for ( ; p <= e; ++p) {
144 if ((qFromUnaligned<T>(p) & mask) == (value & mask))
145 return true;
146 }
147
148 return false;
149}
150
151static inline QByteArray makePattern(const QByteArray &value)
152{
153 QByteArray pattern(value.size(), Qt::Uninitialized);
154 char *data = pattern.data();
155
156 const char *p = value.constData();
157 const char *e = p + value.size();
158 for ( ; p < e; ++p) {
159 if (*p == '\\' && ++p < e) {
160 if (*p == 'x') { // hex (\\xff)
161 char c = 0;
162 for (int i = 0; i < 2 && p + 1 < e; ++i) {
163 ++p;
164 if (const int h = fromHex(*p); h != -1)
165 c = (c << 4) + h;
166 else
167 continue;
168 }
169 *data++ = c;
170 } else if (isOctalDigit(*p)) { // oct (\\7, or \\77, or \\377)
171 char c = *p - '0';
172 if (p + 1 < e && isOctalDigit(p[1])) {
173 c = (c << 3) + *(++p) - '0';
174 if (p + 1 < e && isOctalDigit(p[1]) && p[-1] <= '3')
175 c = (c << 3) + *(++p) - '0';
176 }
177 *data++ = c;
178 } else if (*p == 'n') {
179 *data++ = '\n';
180 } else if (*p == 'r') {
181 *data++ = '\r';
182 } else if (*p == 't') {
183 *data++ = '\t';
184 } else { // escaped
185 *data++ = *p;
186 }
187 } else {
188 *data++ = *p;
189 }
190 }
191 pattern.truncate(data - pattern.data());
192
193 return pattern;
194}
195
196// Evaluate a magic match rule like
197// <match value="must be converted with BinHex" type="string" offset="11"/>
198// <match value="0x9501" type="big16" offset="0:64"/>
199
200QMimeMagicRule::QMimeMagicRule(const QString &type,
201 const QByteArray &value,
202 const QString &offsets,
203 const QByteArray &mask,
204 QString *errorString)
205 : m_type(QMimeMagicRule::type(type.toLatin1())),
206 m_value(value),
207 m_mask(mask),
208 m_matchFunction(nullptr)
209{
210 if (Q_UNLIKELY(m_type == Invalid)) {
211 if (errorString)
212 *errorString = "Type "_L1 + type + " is not supported"_L1;
213 return;
214 }
215
216 // Parse for offset as "1" or "1:10"
217 const qsizetype colonIndex = offsets.indexOf(u':');
218 const QStringView startPosStr = QStringView{offsets}.mid(0, colonIndex); // \ These decay to returning 'offsets'
219 const QStringView endPosStr = QStringView{offsets}.mid(colonIndex + 1);// / unchanged when colonIndex == -1
220 if (Q_UNLIKELY(!QMimeTypeParserBase::parseNumber(startPosStr, &m_startPos, errorString)) ||
221 Q_UNLIKELY(!QMimeTypeParserBase::parseNumber(endPosStr, &m_endPos, errorString))) {
222 m_type = Invalid;
223 return;
224 }
225 if (m_startPos < 0 || m_endPos < 0 || m_endPos < m_startPos) {
226 if (errorString)
227 *errorString = "Invalid offset range \""_L1 + offsets + u'"';
228 m_type = Invalid;
229 return;
230 }
231
232 if (Q_UNLIKELY(m_value.isEmpty())) {
233 m_type = Invalid;
234 if (errorString)
235 *errorString = QStringLiteral("Invalid empty magic rule value");
236 return;
237 }
238
239 if (m_type >= Host16 && m_type <= Byte) {
240 bool ok;
241 m_number = m_value.toUInt(&ok, 0); // autodetect base
242 if (Q_UNLIKELY(!ok)) {
243 m_type = Invalid;
244 if (errorString)
245 *errorString = "Invalid magic rule value \""_L1 + QLatin1StringView(m_value) + u'"';
246 return;
247 }
248 m_numberMask = !m_mask.isEmpty() ? m_mask.toUInt(&ok, 0) : 0; // autodetect base
249 }
250
251 switch (m_type) {
252 case String:
253 m_pattern = makePattern(m_value);
254 m_pattern.squeeze();
255 if (!m_mask.isEmpty()) {
256 if (Q_UNLIKELY(m_mask.size() < 4 || !m_mask.startsWith("0x"))) {
257 m_type = Invalid;
258 if (errorString)
259 *errorString = "Invalid magic rule mask \""_L1 + QLatin1StringView(m_mask) + u'"';
260 return;
261 }
262 const QByteArray &tempMask = QByteArray::fromHex(QByteArray::fromRawData(
263 m_mask.constData() + 2, m_mask.size() - 2));
264 if (Q_UNLIKELY(tempMask.size() != m_pattern.size())) {
265 m_type = Invalid;
266 if (errorString)
267 *errorString = "Invalid magic rule mask size \""_L1 + QLatin1StringView(m_mask) + u'"';
268 return;
269 }
270 m_mask = tempMask;
271 } else {
272 m_mask.fill(char(-1), m_pattern.size());
273 }
274 m_mask.squeeze();
275 m_matchFunction = &QMimeMagicRule::matchString;
276 break;
277 case Byte:
278 if (m_number <= quint8(-1)) {
279 if (m_numberMask == 0)
280 m_numberMask = quint8(-1);
281 m_matchFunction = &QMimeMagicRule::matchNumber<quint8>;
282 }
283 break;
284 case Big16:
285 case Little16:
286 if (m_number <= quint16(-1)) {
287 m_number = m_type == Little16 ? qFromLittleEndian<quint16>(m_number) : qFromBigEndian<quint16>(m_number);
288 if (m_numberMask != 0)
289 m_numberMask = m_type == Little16 ? qFromLittleEndian<quint16>(m_numberMask) : qFromBigEndian<quint16>(m_numberMask);
290 }
291 Q_FALLTHROUGH();
292 case Host16:
293 if (m_number <= quint16(-1)) {
294 if (m_numberMask == 0)
295 m_numberMask = quint16(-1);
296 m_matchFunction = &QMimeMagicRule::matchNumber<quint16>;
297 }
298 break;
299 case Big32:
300 case Little32:
301 m_number = m_type == Little32 ? qFromLittleEndian<quint32>(m_number) : qFromBigEndian<quint32>(m_number);
302 if (m_numberMask != 0)
303 m_numberMask = m_type == Little32 ? qFromLittleEndian<quint32>(m_numberMask) : qFromBigEndian<quint32>(m_numberMask);
304 Q_FALLTHROUGH();
305 case Host32:
306 if (m_numberMask == 0)
307 m_numberMask = quint32(-1);
308 m_matchFunction = &QMimeMagicRule::matchNumber<quint32>;
309 break;
310 default:
311 break;
312 }
313}
314
315QByteArray QMimeMagicRule::mask() const
316{
317 QByteArray result = m_mask;
318 if (m_type == String) {
319 // restore '0x'
320 result = "0x" + result.toHex();
321 }
322 return result;
323}
324
325bool QMimeMagicRule::matches(const QByteArray &data) const
326{
327 const bool ok = m_matchFunction && (this->*m_matchFunction)(data);
328 if (!ok)
329 return false;
330
331 // No submatch? Then we are done.
332 if (m_subMatches.isEmpty())
333 return true;
334
335 //qDebug() << "Checking" << m_subMatches.count() << "sub-rules";
336 // Check that one of the submatches matches too
337 for ( QList<QMimeMagicRule>::const_iterator it = m_subMatches.begin(), end = m_subMatches.end() ;
338 it != end ; ++it ) {
339 if ((*it).matches(data)) {
340 // One of the hierarchies matched -> mimetype recognized.
341 return true;
342 }
343 }
344 return false;
345
346
347}
348
349QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QByteArray makePattern(const QByteArray &value)
static constexpr auto magicRuleTypes