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
qmimeglobpattern.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
6
7#if QT_CONFIG(regularexpression)
8#include <QRegularExpression>
9#endif
10#include <QStringList>
11#include <QDebug>
12
14
15using namespace Qt::StringLiterals;
16
17/*!
18 \internal
19 \class QMimeGlobMatchResult
20 \inmodule QtCore
21 \brief The QMimeGlobMatchResult class accumulates results from glob matching.
22
23 Handles glob weights, and preferring longer matches over shorter matches.
24*/
25
26void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern,
27 qsizetype knownSuffixLength)
28{
29 if (m_allMatchingMimeTypes.contains(mimeType))
30 return;
31 // Is this a lower-weight pattern than the last match? Skip this match then.
32 if (weight < m_weight) {
33 m_allMatchingMimeTypes.append(mimeType);
34 return;
35 }
36 bool replace = weight > m_weight;
37 if (!replace) {
38 // Compare the length of the match
39 if (pattern.size() < m_matchingPatternLength)
40 return; // too short, ignore
41 else if (pattern.size() > m_matchingPatternLength) {
42 // longer: clear any previous match (like *.bz2, when pattern is *.tar.bz2)
43 replace = true;
44 }
45 }
46 if (replace) {
47 m_matchingMimeTypes.clear();
48 // remember the new "longer" length
49 m_matchingPatternLength = pattern.size();
50 m_weight = weight;
51 }
52 if (!m_matchingMimeTypes.contains(mimeType)) {
53 m_matchingMimeTypes.append(mimeType);
54 if (replace)
55 m_allMatchingMimeTypes.prepend(mimeType); // highest-weight first
56 else
57 m_allMatchingMimeTypes.append(mimeType);
58 m_knownSuffixLength = knownSuffixLength;
59 }
60}
61
62QMimeGlobPattern::PatternType QMimeGlobPattern::detectPatternType(QStringView pattern) const
63{
64 const qsizetype patternLength = pattern.size();
65 if (!patternLength)
66 return OtherPattern;
67
68 const qsizetype starCount = pattern.count(u'*');
69 const bool hasSquareBracket = pattern.indexOf(u'[') != -1;
70 const bool hasQuestionMark = pattern.indexOf(u'?') != -1;
71
72 if (!hasSquareBracket && !hasQuestionMark) {
73 if (starCount == 1) {
74 // Patterns like "*~", "*.extension"
75 if (pattern.at(0) == u'*')
76 return SuffixPattern;
77 // Patterns like "README*" (well this is currently the only one like that...)
78 if (pattern.at(patternLength - 1) == u'*')
79 return PrefixPattern;
80 } else if (starCount == 0) {
81 // Names without any wildcards like "README"
82 return LiteralPattern;
83 }
84 }
85
86 if (pattern == "[0-9][0-9][0-9].vdr"_L1)
87 return VdrPattern;
88
89 if (pattern == "*.anim[1-9j]"_L1)
90 return AnimPattern;
91
92 return OtherPattern;
93}
94
95
96/*!
97 \internal
98 \class QMimeGlobPattern
99 \inmodule QtCore
100 \brief The QMimeGlobPattern class contains the glob pattern for file names for MIME type matching.
101
102 \sa QMimeType, QMimeDatabase, QMimeMagicRuleMatcher, QMimeMagicRule
103*/
104
105bool QMimeGlobPattern::matchFileName(const QString &fileName) const
106{
107 // "Applications MUST match globs case-insensitively, except when the case-sensitive
108 // attribute is set to true."
109
110 const qsizetype patternLength = m_pattern.size();
111 if (!patternLength)
112 return false;
113 const qsizetype fileNameLength = fileName.size();
114
115 switch (m_patternType) {
116 case SuffixPattern: {
117 if (fileNameLength + 1 < patternLength)
118 return false;
119 // m_pattern is *.txt, skip the *
120 return fileName.endsWith(QStringView(m_pattern).mid(1), m_caseSensitivity);
121 }
122 case PrefixPattern: {
123 if (fileNameLength + 1 < patternLength)
124 return false;
125 // m_pattern ends with *, stop just before
126 return fileName.startsWith(QStringView(m_pattern).chopped(1), m_caseSensitivity);
127 }
128 case LiteralPattern:
129 return m_pattern.compare(fileName, m_caseSensitivity) == 0;
130 case VdrPattern: // "[0-9][0-9][0-9].vdr" case
131 return fileNameLength == 7
132 && fileName.at(0).isDigit() && fileName.at(1).isDigit() && fileName.at(2).isDigit()
133 && QStringView{fileName}.mid(3, 4).compare(".vdr"_L1, m_caseSensitivity) == 0;
134 case AnimPattern: { // "*.anim[1-9j]" case
135 if (fileNameLength < 6)
136 return false;
137 QChar lastChar = fileName.at(fileNameLength - 1);
138 if (m_caseSensitivity == Qt::CaseInsensitive)
139 lastChar = lastChar.toLower();
140 const bool lastCharOK = (lastChar.isDigit() && lastChar != u'0')
141 || lastChar == u'j';
142 return lastCharOK && QStringView{fileName}.mid(fileNameLength - 6, 5).compare(".anim"_L1, m_caseSensitivity) == 0;
143 }
144 case OtherPattern:
145 // Other fallback patterns: slow but correct method
146#if QT_CONFIG(regularexpression)
147 auto rx = QRegularExpression::fromWildcard(m_pattern);
148 if (m_caseSensitivity == Qt::CaseInsensitive)
149 rx.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
150 return rx.match(fileName).hasMatch();
151#else
152 return false;
153#endif
154 }
155 return false;
156}
157
158static bool isSimplePattern(QStringView pattern)
159{
160 // starts with "*.", has no other '*'
161 return pattern.lastIndexOf(u'*') == 0
162 && pattern.size() > 1
163 && pattern.at(1) == u'.' // (other dots are OK, like *.tar.bz2)
164 // and contains no other special character
165 && !pattern.contains(u'?')
166 && !pattern.contains(u'[')
167 ;
168}
169
170static bool isFastPattern(QStringView pattern)
171{
172 // starts with "*.", has no other '*' and no other '.'
173 return pattern.lastIndexOf(u'*') == 0
174 && pattern.lastIndexOf(u'.') == 1
175 // and contains no other special character
176 && !pattern.contains(u'?')
177 && !pattern.contains(u'[')
178 ;
179}
180
182{
183 const QString &pattern = glob.pattern();
184 Q_ASSERT(!pattern.isEmpty());
185
186 // Store each patterns into either m_fastPatternDict (*.txt, *.html
187 // etc. with default weight 50) or for the rest, like core.*, *.tar.bz2, *~,
188 // into highWeightPatternOffset (>50) or lowWeightPatternOffset (<=50).
189
190 if (glob.weight() == 50 && isFastPattern(pattern) && !glob.isCaseSensitive()) {
191 // The bulk of the patterns is *.foo with weight 50 --> those go into the fast patterns hash.
192 const QString extension = pattern.mid(2).toLower();
193 QStringList &patterns = m_fastPatterns[extension]; // find or create
194 if (!patterns.contains(glob.mimeType()))
195 patterns.append(glob.mimeType());
196 } else {
197 if (glob.weight() > 50) {
198 if (!m_highWeightGlobs.hasPattern(glob.mimeType(), glob.pattern()))
199 m_highWeightGlobs.append(glob);
200 } else {
201 if (!m_lowWeightGlobs.hasPattern(glob.mimeType(), glob.pattern()))
202 m_lowWeightGlobs.append(glob);
203 }
204 }
205}
206
207void QMimeAllGlobPatterns::removeMimeType(const QString &mimeType)
208{
209 for (auto &x : m_fastPatterns)
210 x.removeAll(mimeType);
211 m_highWeightGlobs.removeMimeType(mimeType);
212 m_lowWeightGlobs.removeMimeType(mimeType);
213}
214
215void QMimeGlobPatternList::match(QMimeGlobMatchResult &result, const QString &fileName,
216 const AddMatchFilterFunc &filterFunc) const
217{
218 for (const QMimeGlobPattern &glob : *this) {
219 if (glob.matchFileName(fileName) && filterFunc(glob.mimeType())) {
220 const QString pattern = glob.pattern();
221 const qsizetype suffixLen = isSimplePattern(pattern) ? pattern.size() - strlen("*.") : 0;
222 result.addMatch(glob.mimeType(), glob.weight(), pattern, suffixLen);
223 }
224 }
225}
226
227void QMimeAllGlobPatterns::matchingGlobs(const QString &fileName, QMimeGlobMatchResult &result,
228 const AddMatchFilterFunc &filterFunc) const
229{
230 // First try the high weight matches (>50), if any.
231 m_highWeightGlobs.match(result, fileName, filterFunc);
232
233 // Now use the "fast patterns" dict, for simple *.foo patterns with weight 50
234 // (which is most of them, so this optimization is definitely worth it)
235 const qsizetype lastDot = fileName.lastIndexOf(u'.');
236 if (lastDot != -1) { // if no '.', skip the extension lookup
237 const qsizetype ext_len = fileName.size() - lastDot - 1;
238 const QString simpleExtension = fileName.right(ext_len).toLower();
239 // (toLower because fast patterns are always case-insensitive and saved as lowercase)
240
241 const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension);
242 const QString simplePattern = "*."_L1 + simpleExtension;
243 for (const QString &mime : matchingMimeTypes) {
244 if (filterFunc(mime))
245 result.addMatch(mime, 50, simplePattern, simpleExtension.size());
246 }
247 // Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the
248 // low-weight mimetypes anyway, at least those with weight 50.
249 }
250
251 // Finally, try the low weight matches (<=50)
252 m_lowWeightGlobs.match(result, fileName, filterFunc);
253}
254
256{
257 m_fastPatterns.clear();
258 m_highWeightGlobs.clear();
259 m_lowWeightGlobs.clear();
260}
261
262QT_END_NAMESPACE
Result of the globs parsing, as data structures ready for efficient MIME type matching.
void addGlob(const QMimeGlobPattern &glob)
void matchingGlobs(const QString &fileName, QMimeGlobMatchResult &result, const AddMatchFilterFunc &filterFunc) const
void removeMimeType(const QString &mimeType)
QMimeGlobPatternList m_highWeightGlobs
QMimeGlobPatternList m_lowWeightGlobs
void match(QMimeGlobMatchResult &result, const QString &fileName, const AddMatchFilterFunc &filterFunc) const
The QMimeGlobPattern class contains the glob pattern for file names for MIME type matching.
unsigned weight() const
bool matchFileName(const QString &fileName) const
Combined button and popup list for selecting options.
static bool isSimplePattern(QStringView pattern)
static bool isFastPattern(QStringView pattern)