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 "mimetype.h"
41 
42 #include "mimetype_p.h"
43 #include "mimedatabase_p.h"
44 #include "mimeprovider_p.h"
45 
46 #include "mimeglobpattern_p.h"
47 
48 #include <QtCore/QDebug>
49 #include <QtCore/QLocale>
50 
51 #include <memory>
52 
53 using namespace Utils;
54 using namespace Utils::Internal;
55 
suffixFromPattern(const QString & pattern)56 static QString suffixFromPattern(const QString &pattern)
57 {
58     // Not a simple suffix if it looks like: README or *. or *.* or *.JP*G or *.JP?
59     if (pattern.startsWith(QLatin1String("*.")) &&
60             pattern.length() > 2 &&
61             pattern.indexOf(QLatin1Char('*'), 2) < 0 && pattern.indexOf(QLatin1Char('?'), 2) < 0) {
62         return pattern.mid(2);
63     }
64     return QString();
65 }
66 
MimeTypePrivate()67 MimeTypePrivate::MimeTypePrivate()
68     : loaded(false)
69 {}
70 
MimeTypePrivate(const MimeType & other)71 MimeTypePrivate::MimeTypePrivate(const MimeType &other)
72         : name(other.d->name),
73         localeComments(other.d->localeComments),
74         genericIconName(other.d->genericIconName),
75         iconName(other.d->iconName),
76         globPatterns(other.d->globPatterns),
77         loaded(other.d->loaded)
78 {}
79 
clear()80 void MimeTypePrivate::clear()
81 {
82     name.clear();
83     localeComments.clear();
84     genericIconName.clear();
85     iconName.clear();
86     globPatterns.clear();
87     loaded = false;
88 }
89 
addGlobPattern(const QString & pattern)90 void MimeTypePrivate::addGlobPattern(const QString &pattern)
91 {
92     globPatterns.append(pattern);
93 }
94 
95 /*!
96     \class MimeType
97     \inmodule QtCreator
98     \ingroup shared
99     \brief The MimeType class describes types of file or data, represented by a MIME type string.
100 
101     \since 5.0
102 
103     For instance, a file named \c readme.txt has the MIME type \c text/plain.
104     The MIME type can be determined from the file name, or from the file
105     contents, or from both. MIME type determination can also be done on
106     buffers of data not coming from files.
107 
108     Determining the MIME type of a file can be useful to make sure your
109     application supports it. It is also useful in file-manager-like applications
110     or widgets, in order to display an appropriate \l {MimeType::iconName}{icon} for the file, or even
111     the descriptive \l {MimeType::comment()}{comment} in detailed views.
112 
113     To check if a file has the expected MIME type, you should use inherits()
114     rather than a simple string comparison based on the name(). This is because
115     MIME types can inherit from each other: for instance a C source file is
116     a specific type of plain text file, so \c text/x-csrc inherits \c text/plain.
117  */
118 
119 /*!
120     Constructs this MimeType object initialized with default property values that indicate an invalid MIME type.
121  */
MimeType()122 MimeType::MimeType() :
123         d(new MimeTypePrivate())
124 {
125 }
126 
127 /*!
128     Constructs this MimeType object as a copy of \a other.
129  */
130 MimeType::MimeType(const MimeType &other) = default;
131 
132 /*!
133     Assigns the data of \a other to this MimeType object, and returns a reference to this object.
134  */
operator =(const MimeType & other)135 MimeType &MimeType::operator=(const MimeType &other)
136 {
137     if (d != other.d)
138         d = other.d;
139     return *this;
140 }
141 
142 /*!
143     \fn MimeType::MimeType(const Internal::MimeTypePrivate &dd)
144     \internal
145  */
MimeType(const MimeTypePrivate & dd)146 MimeType::MimeType(const MimeTypePrivate &dd) :
147         d(new MimeTypePrivate(dd))
148 {
149 }
150 
151 /*!
152     Destroys the MimeType object, and releases the d pointer.
153  */
154 MimeType::~MimeType() = default;
155 
156 /*!
157     Returns \c true if \a other equals this MimeType object, otherwise returns \c false.
158     The name is the unique identifier for a MIME type, so two  MIME types with
159     the same name are equal.
160  */
operator ==(const MimeType & other) const161 bool MimeType::operator==(const MimeType &other) const
162 {
163     return d == other.d || d->name == other.d->name;
164 }
165 
166 /*!
167     \fn bool MimeType::operator!=(const MimeType &other) const;
168     Returns \c true if \a other does not equal this MimeType object, otherwise returns \c false.
169  */
170 
171 /*!
172     \fn inline uint Utils::qHash(const MimeType &mime)
173     \internal
174 */
175 
176 /*!
177     Returns \c true if the MimeType object contains valid data, otherwise returns \c false.
178     A valid MIME type has a non-empty name().
179     The invalid MIME type is the default-constructed MimeType.
180  */
isValid() const181 bool MimeType::isValid() const
182 {
183     return !d->name.isEmpty();
184 }
185 
186 /*!
187     Returns \c true if this MIME type is the default MIME type which
188     applies to all files: \c application/octet-stream.
189  */
isDefault() const190 bool MimeType::isDefault() const
191 {
192     return d->name == MimeDatabasePrivate::instance()->defaultMimeType();
193 }
194 
195 /*!
196     Returns the name of the MIME type.
197  */
name() const198 QString MimeType::name() const
199 {
200     return d->name;
201 }
202 
203 /*!
204     Returns the description of the MIME type to be displayed on user interfaces.
205 
206     The system language (QLocale::system().name()) is used to select the appropriate translation.
207  */
comment() const208 QString MimeType::comment() const
209 {
210     MimeDatabasePrivate::instance()->provider()->loadMimeTypePrivate(*d);
211 
212     QStringList languageList;
213     languageList << QLocale::system().name();
214     languageList << QLocale::system().uiLanguages();
215     Q_FOREACH (const QString &language, languageList) {
216         const QString lang = language == QLatin1String("C") ? QLatin1String("en_US") : language;
217         const QString comm = d->localeComments.value(lang);
218         if (!comm.isEmpty())
219             return comm;
220         const int pos = lang.indexOf(QLatin1Char('_'));
221         if (pos != -1) {
222             // "pt_BR" not found? try just "pt"
223             const QString shortLang = lang.left(pos);
224             const QString commShort = d->localeComments.value(shortLang);
225             if (!commShort.isEmpty())
226                 return commShort;
227         }
228     }
229 
230     // Use the mimetype name as fallback
231     return d->name;
232 }
233 
234 /*!
235     Returns the file name of a generic icon that represents the MIME type.
236 
237     This should be used if the icon returned by iconName() cannot be found on
238     the system. It is used for categories of similar types (like spreadsheets
239     or archives) that can use a common icon.
240     The freedesktop.org Icon Naming Specification lists a set of such icon names.
241 
242     The icon name can be given to QIcon::fromTheme() in order to load the icon.
243  */
genericIconName() const244 QString MimeType::genericIconName() const
245 {
246     MimeDatabasePrivate::instance()->provider()->loadGenericIcon(*d);
247     if (d->genericIconName.isEmpty()) {
248         // From the spec:
249         // If the generic icon name is empty (not specified by the mimetype definition)
250         // then the mimetype is used to generate the generic icon by using the top-level
251         // media type (e.g.  "video" in "video/ogg") and appending "-x-generic"
252         // (i.e. "video-x-generic" in the previous example).
253         QString group = name();
254         const int slashindex = group.indexOf(QLatin1Char('/'));
255         if (slashindex != -1)
256             group = group.left(slashindex);
257         return group + QLatin1String("-x-generic");
258     }
259     return d->genericIconName;
260 }
261 
262 /*!
263     Returns the file name of an icon image that represents the MIME type.
264 
265     The icon name can be given to QIcon::fromTheme() in order to load the icon.
266  */
iconName() const267 QString MimeType::iconName() const
268 {
269     MimeDatabasePrivate::instance()->provider()->loadIcon(*d);
270     if (d->iconName.isEmpty()) {
271         // Make default icon name from the mimetype name
272         d->iconName = name();
273         const int slashindex = d->iconName.indexOf(QLatin1Char('/'));
274         if (slashindex != -1)
275             d->iconName[slashindex] = QLatin1Char('-');
276     }
277     return d->iconName;
278 }
279 
280 /*!
281     Returns the list of glob matching patterns.
282  */
globPatterns() const283 QStringList MimeType::globPatterns() const
284 {
285     MimeDatabasePrivate::instance()->provider()->loadMimeTypePrivate(*d);
286     return d->globPatterns;
287 }
288 
289 /*!
290     A type is a subclass of another type if any instance of the first type is
291     also an instance of the second. For example, all \c image/svg+xml files are
292     also \c text/xml, \c text/plain and \c application/octet-stream files.
293 
294     Subclassing is about the format, rather than the category of the data.
295     For example, there is no \e {generic spreadsheet} class that all
296     spreadsheets inherit from.
297 
298     Conversely, the parent  MIME type of \c image/svg+xml is \c text/xml.
299 
300     A  MIME type can have multiple parents. For instance, \c application/x-perl
301     has two parents: \c application/x-executable and \c text/plain. This makes
302     it possible to both execute perl scripts, and to open them in text editors.
303 */
parentMimeTypes() const304 QStringList MimeType::parentMimeTypes() const
305 {
306     return MimeDatabasePrivate::instance()->provider()->parents(d->name);
307 }
308 
collectParentMimeTypes(const QString & mime,QStringList & allParents)309 static void collectParentMimeTypes(const QString &mime, QStringList &allParents)
310 {
311     const QStringList parents = MimeDatabasePrivate::instance()->provider()->parents(mime);
312     for (const QString &parent : parents) {
313         // I would use QSet, but since order matters I better not
314         if (!allParents.contains(parent))
315             allParents.append(parent);
316     }
317     // We want a breadth-first search, so that the least-specific parent (octet-stream) is last
318     // This means iterating twice, unfortunately.
319     for (const QString &parent : parents)
320         collectParentMimeTypes(parent, allParents);
321 }
322 
323 /*!
324     Returns all the parent MIME types of this type, direct and indirect.
325     This includes grandparents, and so on.
326 
327     For instance, for \c image/svg+xml the list would be:
328     \c application/xml, \c text/plain, \c application/octet-stream.
329 
330     \note The \c application/octet-stream type is the ultimate parent for all types
331     of files (but not directories).
332 */
allAncestors() const333 QStringList MimeType::allAncestors() const
334 {
335     QStringList allParents;
336     collectParentMimeTypes(d->name, allParents);
337     return allParents;
338 }
339 
340 /*!
341     Returns the list of aliases of this MIME type.
342 
343     For instance, for \c text/csv, the returned list would be:
344     \c text/x-csv, \c text/x-comma-separated-values.
345 
346     \note All MimeType instances refer to proper  MIME types,
347     never to aliases directly.
348 
349     The order of the aliases in the list is undefined.
350 */
aliases() const351 QStringList MimeType::aliases() const
352 {
353     return MimeDatabasePrivate::instance()->provider()->listAliases(d->name);
354 }
355 
356 /*!
357     Returns the known suffixes for the MIME type.
358     No leading dot is included, so for instance this would return
359     \c {"jpg", "jpeg"} for \c image/jpeg.
360  */
suffixes() const361 QStringList MimeType::suffixes() const
362 {
363     MimeDatabasePrivate::instance()->provider()->loadMimeTypePrivate(*d);
364 
365     QStringList result;
366     for (const QString &pattern : qAsConst(d->globPatterns)) {
367         const QString suffix = suffixFromPattern(pattern);
368         if (!suffix.isEmpty())
369             result.append(suffix);
370     }
371 
372     return result;
373 }
374 
375 /*!
376     Returns the preferred suffix for the MIME type.
377     No leading dot is included, so for instance this would return \c "pdf" for
378     \c application/pdf. The return value can be empty, for MIME types which do
379     not have any suffixes associated.
380  */
preferredSuffix() const381 QString MimeType::preferredSuffix() const
382 {
383     const QStringList suffixList = suffixes();
384     return suffixList.isEmpty() ? QString() : suffixList.at(0);
385 }
386 
387 /*!
388     Returns a filter string usable for a file dialog.
389 */
filterString() const390 QString MimeType::filterString() const
391 {
392     MimeDatabasePrivate::instance()->provider()->loadMimeTypePrivate(*d);
393     QString filter;
394 
395     if (!d->globPatterns.empty()) {
396         filter += comment() + QLatin1String(" (");
397         for (int i = 0; i < d->globPatterns.size(); ++i) {
398             if (i != 0)
399                 filter += QLatin1Char(' ');
400             filter += d->globPatterns.at(i);
401         }
402         filter +=  QLatin1Char(')');
403     }
404 
405     return filter;
406 }
407 
408 /*!
409     Returns \c true if the name or alias of the MIME type matches
410     \a nameOrAlias.
411 */
matchesName(const QString & nameOrAlias) const412 bool MimeType::matchesName(const QString &nameOrAlias) const
413 {
414     if (d->name == nameOrAlias)
415         return true;
416     return MimeDatabasePrivate::instance()->provider()->resolveAlias(nameOrAlias) == d->name;
417 }
418 
419 /*!
420     Sets the preferred filename suffix for the MIME type to \a suffix.
421 */
setPreferredSuffix(const QString & suffix)422 void MimeType::setPreferredSuffix(const QString &suffix)
423 {
424     MimeDatabasePrivate::instance()->provider()->loadMimeTypePrivate(*d);
425 
426     auto it = std::find_if(d->globPatterns.begin(), d->globPatterns.end(),
427                            [suffix](const QString &pattern) {
428                                return suffixFromPattern(pattern) == suffix;
429                            });
430     if (it != d->globPatterns.end())
431         d->globPatterns.erase(it);
432     d->globPatterns.prepend(QLatin1String("*.") + suffix);
433 }
434 
435 /*!
436     Returns \c true if this MIME type is \a mimeTypeName or inherits it,
437     or if \a mimeTypeName is an alias for this mimetype.
438 
439     \sa parentMimeTypes()
440  */
inherits(const QString & mimeTypeName) const441 bool MimeType::inherits(const QString &mimeTypeName) const
442 {
443     if (d->name == mimeTypeName)
444         return true;
445     return MimeDatabasePrivate::instance()->inherits(d->name, mimeTypeName);
446 }
447 
448 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug debug,const MimeType & mime)449 QDebug operator<<(QDebug debug, const MimeType &mime)
450 {
451     QDebugStateSaver saver(debug);
452     if (!mime.isValid()) {
453         debug.nospace() << "MimeType(invalid)";
454     } else {
455         debug.nospace() << "MimeType(" << mime.name() << ")";
456     }
457     return debug;
458 }
459 #endif
460