1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qmimeglobpattern_p.h"
41
42#if QT_CONFIG(regularexpression)
43#include <QRegularExpression>
44#endif
45#include <QStringList>
46#include <QDebug>
47
48QT_BEGIN_NAMESPACE
49
50/*!
51 \internal
52 \class QMimeGlobMatchResult
53 \inmodule QtCore
54 \brief The QMimeGlobMatchResult class accumulates results from glob matching.
55
56 Handles glob weights, and preferring longer matches over shorter matches.
57*/
58
59void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern, int knownSuffixLength)
60{
61 if (m_allMatchingMimeTypes.contains(str: mimeType))
62 return;
63 // Is this a lower-weight pattern than the last match? Skip this match then.
64 if (weight < m_weight) {
65 m_allMatchingMimeTypes.append(t: mimeType);
66 return;
67 }
68 bool replace = weight > m_weight;
69 if (!replace) {
70 // Compare the length of the match
71 if (pattern.length() < m_matchingPatternLength)
72 return; // too short, ignore
73 else if (pattern.length() > m_matchingPatternLength) {
74 // longer: clear any previous match (like *.bz2, when pattern is *.tar.bz2)
75 replace = true;
76 }
77 }
78 if (replace) {
79 m_matchingMimeTypes.clear();
80 // remember the new "longer" length
81 m_matchingPatternLength = pattern.length();
82 m_weight = weight;
83 }
84 if (!m_matchingMimeTypes.contains(str: mimeType)) {
85 m_matchingMimeTypes.append(t: mimeType);
86 m_allMatchingMimeTypes.append(t: mimeType);
87 m_knownSuffixLength = knownSuffixLength;
88 }
89}
90
91QMimeGlobPattern::PatternType QMimeGlobPattern::detectPatternType(const QString &pattern) const
92{
93 const int patternLength = pattern.length();
94 if (!patternLength)
95 return OtherPattern;
96
97 const int starCount = pattern.count(c: QLatin1Char('*'));
98 const bool hasSquareBracket = pattern.indexOf(c: QLatin1Char('[')) != -1;
99 const bool hasQuestionMark = pattern.indexOf(c: QLatin1Char('?')) != -1;
100
101 if (!hasSquareBracket && !hasQuestionMark) {
102 if (starCount == 1) {
103 // Patterns like "*~", "*.extension"
104 if (pattern.at(i: 0) == QLatin1Char('*'))
105 return SuffixPattern;
106 // Patterns like "README*" (well this is currently the only one like that...)
107 if (pattern.at(i: patternLength - 1) == QLatin1Char('*'))
108 return PrefixPattern;
109 } else if (starCount == 0) {
110 // Names without any wildcards like "README"
111 return LiteralPattern;
112 }
113 }
114
115 if (pattern == QLatin1String("[0-9][0-9][0-9].vdr"))
116 return VdrPattern;
117
118 if (pattern == QLatin1String("*.anim[1-9j]"))
119 return AnimPattern;
120
121 return OtherPattern;
122}
123
124
125/*!
126 \internal
127 \class QMimeGlobPattern
128 \inmodule QtCore
129 \brief The QMimeGlobPattern class contains the glob pattern for file names for MIME type matching.
130
131 \sa QMimeType, QMimeDatabase, QMimeMagicRuleMatcher, QMimeMagicRule
132*/
133
134bool QMimeGlobPattern::matchFileName(const QString &inputFileName) const
135{
136 // "Applications MUST match globs case-insensitively, except when the case-sensitive
137 // attribute is set to true."
138 // The constructor takes care of putting case-insensitive patterns in lowercase.
139 const QString fileName = m_caseSensitivity == Qt::CaseInsensitive
140 ? inputFileName.toLower() : inputFileName;
141
142 const int patternLength = m_pattern.length();
143 if (!patternLength)
144 return false;
145 const int fileNameLength = fileName.length();
146
147 switch (m_patternType) {
148 case SuffixPattern: {
149 if (fileNameLength + 1 < patternLength)
150 return false;
151
152 const QChar *c1 = m_pattern.unicode() + patternLength - 1;
153 const QChar *c2 = fileName.unicode() + fileNameLength - 1;
154 int cnt = 1;
155 while (cnt < patternLength && *c1-- == *c2--)
156 ++cnt;
157 return cnt == patternLength;
158 }
159 case PrefixPattern: {
160 if (fileNameLength + 1 < patternLength)
161 return false;
162
163 const QChar *c1 = m_pattern.unicode();
164 const QChar *c2 = fileName.unicode();
165 int cnt = 1;
166 while (cnt < patternLength && *c1++ == *c2++)
167 ++cnt;
168 return cnt == patternLength;
169 }
170 case LiteralPattern:
171 return (m_pattern == fileName);
172 case VdrPattern: // "[0-9][0-9][0-9].vdr" case
173 return fileNameLength == 7
174 && fileName.at(i: 0).isDigit() && fileName.at(i: 1).isDigit() && fileName.at(i: 2).isDigit()
175 && QStringView{fileName}.mid(pos: 3, n: 4) == QLatin1String(".vdr");
176 case AnimPattern: { // "*.anim[1-9j]" case
177 if (fileNameLength < 6)
178 return false;
179 const QChar lastChar = fileName.at(i: fileNameLength - 1);
180 const bool lastCharOK = (lastChar.isDigit() && lastChar != QLatin1Char('0'))
181 || lastChar == QLatin1Char('j');
182 return lastCharOK && QStringView{fileName}.mid(pos: fileNameLength - 6, n: 5) == QLatin1String(".anim");
183 }
184 case OtherPattern:
185 // Other fallback patterns: slow but correct method
186#if QT_CONFIG(regularexpression)
187 QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(str: m_pattern));
188 return rx.match(subject: fileName).hasMatch();
189#else
190 return false;
191#endif
192 }
193 return false;
194}
195
196static bool isSimplePattern(const QString &pattern)
197{
198 // starts with "*.", has no other '*'
199 return pattern.lastIndexOf(c: QLatin1Char('*')) == 0
200 && pattern.length() > 1
201 && pattern.at(i: 1) == QLatin1Char('.') // (other dots are OK, like *.tar.bz2)
202 // and contains no other special character
203 && !pattern.contains(c: QLatin1Char('?'))
204 && !pattern.contains(c: QLatin1Char('['))
205 ;
206}
207
208static bool isFastPattern(const QString &pattern)
209{
210 // starts with "*.", has no other '*' and no other '.'
211 return pattern.lastIndexOf(c: QLatin1Char('*')) == 0
212 && pattern.lastIndexOf(c: QLatin1Char('.')) == 1
213 // and contains no other special character
214 && !pattern.contains(c: QLatin1Char('?'))
215 && !pattern.contains(c: QLatin1Char('['))
216 ;
217}
218
219void QMimeAllGlobPatterns::addGlob(const QMimeGlobPattern &glob)
220{
221 const QString &pattern = glob.pattern();
222 Q_ASSERT(!pattern.isEmpty());
223
224 // Store each patterns into either m_fastPatternDict (*.txt, *.html etc. with default weight 50)
225 // or for the rest, like core.*, *.tar.bz2, *~, into highWeightPatternOffset (>50)
226 // or lowWeightPatternOffset (<=50)
227
228 if (glob.weight() == 50 && isFastPattern(pattern) && !glob.isCaseSensitive()) {
229 // The bulk of the patterns is *.foo with weight 50 --> those go into the fast patterns hash.
230 const QString extension = pattern.mid(position: 2).toLower();
231 QStringList &patterns = m_fastPatterns[extension]; // find or create
232 if (!patterns.contains(str: glob.mimeType()))
233 patterns.append(t: glob.mimeType());
234 } else {
235 if (glob.weight() > 50) {
236 if (!m_highWeightGlobs.hasPattern(mimeType: glob.mimeType(), pattern: glob.pattern()))
237 m_highWeightGlobs.append(t: glob);
238 } else {
239 if (!m_lowWeightGlobs.hasPattern(mimeType: glob.mimeType(), pattern: glob.pattern()))
240 m_lowWeightGlobs.append(t: glob);
241 }
242 }
243}
244
245void QMimeAllGlobPatterns::removeMimeType(const QString &mimeType)
246{
247 for (auto &x : m_fastPatterns)
248 x.removeAll(t: mimeType);
249 m_highWeightGlobs.removeMimeType(mimeType);
250 m_lowWeightGlobs.removeMimeType(mimeType);
251}
252
253void QMimeGlobPatternList::match(QMimeGlobMatchResult &result,
254 const QString &fileName) const
255{
256
257 QMimeGlobPatternList::const_iterator it = this->constBegin();
258 const QMimeGlobPatternList::const_iterator endIt = this->constEnd();
259 for (; it != endIt; ++it) {
260 const QMimeGlobPattern &glob = *it;
261 if (glob.matchFileName(inputFileName: fileName)) {
262 const QString pattern = glob.pattern();
263 const int suffixLen = isSimplePattern(pattern) ? pattern.length() - 2 : 0;
264 result.addMatch(mimeType: glob.mimeType(), weight: glob.weight(), pattern, knownSuffixLength: suffixLen);
265 }
266 }
267}
268
269void QMimeAllGlobPatterns::matchingGlobs(const QString &fileName, QMimeGlobMatchResult &result) const
270{
271 // First try the high weight matches (>50), if any.
272 m_highWeightGlobs.match(result, fileName);
273
274 // Now use the "fast patterns" dict, for simple *.foo patterns with weight 50
275 // (which is most of them, so this optimization is definitely worth it)
276 const int lastDot = fileName.lastIndexOf(c: QLatin1Char('.'));
277 if (lastDot != -1) { // if no '.', skip the extension lookup
278 const int ext_len = fileName.length() - lastDot - 1;
279 const QString simpleExtension = fileName.right(n: ext_len).toLower();
280 // (toLower because fast patterns are always case-insensitive and saved as lowercase)
281
282 const QStringList matchingMimeTypes = m_fastPatterns.value(akey: simpleExtension);
283 const QString simplePattern = QLatin1String("*.") + simpleExtension;
284 for (const QString &mime : matchingMimeTypes)
285 result.addMatch(mimeType: mime, weight: 50, pattern: simplePattern, knownSuffixLength: simpleExtension.size());
286 // Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the low-weight mimetypes anyway,
287 // at least those with weight 50.
288 }
289
290 // Finally, try the low weight matches (<=50)
291 m_lowWeightGlobs.match(result, fileName);
292}
293
294void QMimeAllGlobPatterns::clear()
295{
296 m_fastPatterns.clear();
297 m_highWeightGlobs.clear();
298 m_lowWeightGlobs.clear();
299}
300
301QT_END_NAMESPACE
302

source code of qtbase/src/corelib/mimetypes/qmimeglobpattern.cpp