Mimetypes v3: Copy version from Qt 6.3

State from qtbase/67b2eb8b302c19c9b3a80028e20141bde9aef01a
from 6.3 branch

Change-Id: If1be497af3907f31f1388e9db42150cf9ae60d66
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Eike Ziller
2022-02-18 09:23:10 +01:00
parent 05d34a7e1a
commit e2ccf20c66
16 changed files with 4532 additions and 0 deletions

View File

@@ -0,0 +1,832 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qplatformdefs.h> // always first
#include "qmimedatabase.h"
#include "qmimedatabase_p.h"
#include "qmimeprovider_p.h"
#include "qmimetype_p.h"
#include <private/qfilesystementry_p.h>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QStandardPaths>
#include <QtCore/QBuffer>
#include <QtCore/QUrl>
#include <QtCore/QDebug>
#include <algorithm>
#include <functional>
#include <stack>
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QMimeDatabasePrivate, staticQMimeDatabase)
QMimeDatabasePrivate *QMimeDatabasePrivate::instance()
{
return staticQMimeDatabase();
}
QMimeDatabasePrivate::QMimeDatabasePrivate()
: m_defaultMimeType(QLatin1String("application/octet-stream"))
{
}
QMimeDatabasePrivate::~QMimeDatabasePrivate()
{
}
#ifdef QT_BUILD_INTERNAL
Q_CORE_EXPORT
#else
static const
#endif
int qmime_secondsBetweenChecks = 5;
bool QMimeDatabasePrivate::shouldCheck()
{
if (m_lastCheck.isValid() && m_lastCheck.elapsed() < qmime_secondsBetweenChecks * 1000)
return false;
m_lastCheck.start();
return true;
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY)
# define QT_USE_MMAP
#endif
void QMimeDatabasePrivate::loadProviders()
{
// We use QStandardPaths every time to check if new files appeared
const QStringList mimeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime"), QStandardPaths::LocateDirectory);
const auto fdoIterator = std::find_if(mimeDirs.constBegin(), mimeDirs.constEnd(), [](const QString &mimeDir) -> bool {
return QFileInfo::exists(mimeDir + QStringLiteral("/packages/freedesktop.org.xml")); }
);
const bool needInternalDB = QMimeXMLProvider::InternalDatabaseAvailable && fdoIterator == mimeDirs.constEnd();
//qDebug() << "mime dirs:" << mimeDirs;
Providers currentProviders;
std::swap(m_providers, currentProviders);
m_providers.reserve(mimeDirs.size() + (needInternalDB ? 1 : 0));
for (const QString &mimeDir : mimeDirs) {
const QString cacheFile = mimeDir + QStringLiteral("/mime.cache");
// Check if we already have a provider for this dir
const auto predicate = [mimeDir](const std::unique_ptr<QMimeProviderBase> &prov)
{
return prov && prov->directory() == mimeDir;
};
const auto it = std::find_if(currentProviders.begin(), currentProviders.end(), predicate);
if (it == currentProviders.end()) {
std::unique_ptr<QMimeProviderBase> provider;
#if defined(QT_USE_MMAP)
if (qEnvironmentVariableIsEmpty("QT_NO_MIME_CACHE") && QFileInfo::exists(cacheFile)) {
provider.reset(new QMimeBinaryProvider(this, mimeDir));
//qDebug() << "Created binary provider for" << mimeDir;
if (!provider->isValid()) {
provider.reset();
}
}
#endif
if (!provider) {
provider.reset(new QMimeXMLProvider(this, mimeDir));
//qDebug() << "Created XML provider for" << mimeDir;
}
m_providers.push_back(std::move(provider));
} else {
auto provider = std::move(*it); // take provider out of the vector
provider->ensureLoaded();
if (!provider->isValid()) {
provider.reset(new QMimeXMLProvider(this, mimeDir));
//qDebug() << "Created XML provider to replace binary provider for" << mimeDir;
}
m_providers.push_back(std::move(provider));
}
}
// mimeDirs is sorted "most local first, most global last"
// so the internal XML DB goes at the end
if (needInternalDB) {
// Check if we already have a provider for the InternalDatabase
const auto isInternal = [](const std::unique_ptr<QMimeProviderBase> &prov)
{
return prov && prov->isInternalDatabase();
};
const auto it = std::find_if(currentProviders.begin(), currentProviders.end(), isInternal);
if (it == currentProviders.end()) {
m_providers.push_back(Providers::value_type(new QMimeXMLProvider(this, QMimeXMLProvider::InternalDatabase)));
} else {
m_providers.push_back(std::move(*it));
}
}
}
const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers()
{
#ifndef Q_OS_WASM // stub implementation always returns true
Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex
#endif
if (m_providers.empty()) {
loadProviders();
m_lastCheck.start();
} else {
if (shouldCheck())
loadProviders();
}
return m_providers;
}
QString QMimeDatabasePrivate::resolveAlias(const QString &nameOrAlias)
{
for (const auto &provider : providers()) {
const QString ret = provider->resolveAlias(nameOrAlias);
if (!ret.isEmpty())
return ret;
}
return nameOrAlias;
}
/*!
\internal
Returns a MIME type or an invalid one if none found
*/
QMimeType QMimeDatabasePrivate::mimeTypeForName(const QString &nameOrAlias)
{
const QString mimeName = resolveAlias(nameOrAlias);
for (const auto &provider : providers()) {
const QMimeType mime = provider->mimeTypeForName(mimeName);
if (mime.isValid())
return mime;
}
return {};
}
QStringList QMimeDatabasePrivate::mimeTypeForFileName(const QString &fileName)
{
if (fileName.endsWith(QLatin1Char('/')))
return QStringList() << QLatin1String("inode/directory");
const QMimeGlobMatchResult result = findByFileName(fileName);
QStringList matchingMimeTypes = result.m_matchingMimeTypes;
matchingMimeTypes.sort(); // make it deterministic
return matchingMimeTypes;
}
QMimeGlobMatchResult QMimeDatabasePrivate::findByFileName(const QString &fileName)
{
QMimeGlobMatchResult result;
const QString fileNameExcludingPath = QFileSystemEntry(fileName).fileName();
for (const auto &provider : providers())
provider->addFileNameMatches(fileNameExcludingPath, result);
return result;
}
void QMimeDatabasePrivate::loadMimeTypePrivate(QMimeTypePrivate &mimePrivate)
{
QMutexLocker locker(&mutex);
if (mimePrivate.name.isEmpty())
return; // invalid mimetype
if (!mimePrivate.loaded) { // XML provider sets loaded=true, binary provider does this on demand
Q_ASSERT(mimePrivate.fromCache);
bool found = false;
for (const auto &provider : providers()) {
if (provider->loadMimeTypePrivate(mimePrivate)) {
found = true;
break;
}
}
if (!found) {
const QString file = mimePrivate.name + QLatin1String(".xml");
qWarning() << "No file found for" << file << ", even though update-mime-info said it would exist.\n"
"Either it was just removed, or the directory doesn't have executable permission..."
<< QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime"), QStandardPaths::LocateDirectory);
}
mimePrivate.loaded = true;
}
}
void QMimeDatabasePrivate::loadGenericIcon(QMimeTypePrivate &mimePrivate)
{
QMutexLocker locker(&mutex);
if (mimePrivate.fromCache) {
mimePrivate.genericIconName.clear();
for (const auto &provider : providers()) {
provider->loadGenericIcon(mimePrivate);
if (!mimePrivate.genericIconName.isEmpty())
break;
}
}
}
void QMimeDatabasePrivate::loadIcon(QMimeTypePrivate &mimePrivate)
{
QMutexLocker locker(&mutex);
if (mimePrivate.fromCache) {
mimePrivate.iconName.clear();
for (const auto &provider : providers()) {
provider->loadIcon(mimePrivate);
if (!mimePrivate.iconName.isEmpty())
break;
}
}
}
static QString fallbackParent(const QString &mimeTypeName)
{
const QStringView myGroup = QStringView{mimeTypeName}.left(mimeTypeName.indexOf(QLatin1Char('/')));
// All text/* types are subclasses of text/plain.
if (myGroup == QLatin1String("text") && mimeTypeName != QLatin1String("text/plain"))
return QLatin1String("text/plain");
// All real-file mimetypes implicitly derive from application/octet-stream
if (myGroup != QLatin1String("inode") &&
// ignore non-file extensions
myGroup != QLatin1String("all") && myGroup != QLatin1String("fonts") && myGroup != QLatin1String("print") && myGroup != QLatin1String("uri")
&& mimeTypeName != QLatin1String("application/octet-stream")) {
return QLatin1String("application/octet-stream");
}
return QString();
}
QStringList QMimeDatabasePrivate::mimeParents(const QString &mimeName)
{
QMutexLocker locker(&mutex);
return parents(mimeName);
}
QStringList QMimeDatabasePrivate::parents(const QString &mimeName)
{
Q_ASSERT(!mutex.tryLock());
QStringList result;
for (const auto &provider : providers())
provider->addParents(mimeName, result);
if (result.isEmpty()) {
const QString parent = fallbackParent(mimeName);
if (!parent.isEmpty())
result.append(parent);
}
return result;
}
QStringList QMimeDatabasePrivate::listAliases(const QString &mimeName)
{
QMutexLocker locker(&mutex);
QStringList result;
for (const auto &provider : providers())
provider->addAliases(mimeName, result);
return result;
}
bool QMimeDatabasePrivate::mimeInherits(const QString &mime, const QString &parent)
{
QMutexLocker locker(&mutex);
return inherits(mime, parent);
}
static inline bool isTextFile(const QByteArray &data)
{
// UTF16 byte order marks
static const char bigEndianBOM[] = "\xFE\xFF";
static const char littleEndianBOM[] = "\xFF\xFE";
if (data.startsWith(bigEndianBOM) || data.startsWith(littleEndianBOM))
return true;
// Check the first 128 bytes (see shared-mime spec)
const char *p = data.constData();
const char *e = p + qMin(128, data.size());
for ( ; p < e; ++p) {
if (static_cast<unsigned char>(*p) < 32 && *p != 9 && *p !=10 && *p != 13)
return false;
}
return true;
}
QMimeType QMimeDatabasePrivate::findByData(const QByteArray &data, int *accuracyPtr)
{
if (data.isEmpty()) {
*accuracyPtr = 100;
return mimeTypeForName(QLatin1String("application/x-zerosize"));
}
*accuracyPtr = 0;
QMimeType candidate;
for (const auto &provider : providers())
provider->findByMagic(data, accuracyPtr, candidate);
if (candidate.isValid())
return candidate;
if (isTextFile(data)) {
*accuracyPtr = 5;
return mimeTypeForName(QLatin1String("text/plain"));
}
return mimeTypeForName(defaultMimeType());
}
QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device, int *accuracyPtr)
{
// First, glob patterns are evaluated. If there is a match with max weight,
// this one is selected and we are done. Otherwise, the file contents are
// evaluated and the match with the highest value (either a magic priority or
// a glob pattern weight) is selected. Matching starts from max level (most
// specific) in both cases, even when there is already a suffix matching candidate.
*accuracyPtr = 0;
// Pass 1) Try to match on the file name
QMimeGlobMatchResult candidatesByName = findByFileName(fileName);
if (candidatesByName.m_allMatchingMimeTypes.count() == 1) {
*accuracyPtr = 100;
const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
if (mime.isValid())
return mime;
candidatesByName = {};
}
// Extension is unknown, or matches multiple mimetypes.
// Pass 2) Match on content, if we can read the data
const auto matchOnContent = [this, accuracyPtr, &candidatesByName](QIODevice *device) {
if (device->isOpen()) {
// Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h).
// This is much faster than seeking back and forth into QIODevice.
const QByteArray data = device->peek(16384);
int magicAccuracy = 0;
QMimeType candidateByData(findByData(data, &magicAccuracy));
// Disambiguate conflicting extensions (if magic matching found something)
if (candidateByData.isValid() && magicAccuracy > 0) {
const QString sniffedMime = candidateByData.name();
// If the sniffedMime matches a highest-weight glob match, use it
if (candidatesByName.m_matchingMimeTypes.contains(sniffedMime)) {
*accuracyPtr = 100;
return candidateByData;
}
for (const QString &m : qAsConst(candidatesByName.m_allMatchingMimeTypes)) {
if (inherits(m, sniffedMime)) {
// We have magic + pattern pointing to this, so it's a pretty good match
*accuracyPtr = 100;
return mimeTypeForName(m);
}
}
if (candidatesByName.m_allMatchingMimeTypes.isEmpty()) {
// No glob, use magic
*accuracyPtr = magicAccuracy;
return candidateByData;
}
}
}
if (candidatesByName.m_allMatchingMimeTypes.count() > 1) {
candidatesByName.m_matchingMimeTypes.sort(); // make it deterministic
*accuracyPtr = 20;
const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
if (mime.isValid())
return mime;
}
return mimeTypeForName(defaultMimeType());
};
if (device)
return matchOnContent(device);
QFile fallbackFile(fileName);
fallbackFile.open(QIODevice::ReadOnly); // error handling: matchOnContent() will check isOpen()
return matchOnContent(&fallbackFile);
}
QList<QMimeType> QMimeDatabasePrivate::allMimeTypes()
{
QList<QMimeType> result;
for (const auto &provider : providers())
provider->addAllMimeTypes(result);
return result;
}
bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
{
const QString resolvedParent = resolveAlias(parent);
std::stack<QString, QStringList> toCheck;
toCheck.push(mime);
while (!toCheck.empty()) {
if (toCheck.top() == resolvedParent)
return true;
const QString mimeName = toCheck.top();
toCheck.pop();
const auto parentList = parents(mimeName);
for (const QString &par : parentList)
toCheck.push(resolveAlias(par));
}
return false;
}
/*!
\class QMimeDatabase
\inmodule QtCore
\brief The QMimeDatabase class maintains a database of MIME types.
\since 5.0
The MIME type database is provided by the freedesktop.org shared-mime-info
project. If the MIME type database cannot be found on the system, as is the case
on most Windows, \macos, and iOS systems, Qt will use its own copy of it.
Applications which want to define custom MIME types need to install an
XML file into the locations searched for MIME definitions.
These locations can be queried with
\snippet code/src_corelib_mimetype_qmimedatabase.cpp 1
On a typical Unix system, this will be /usr/share/mime/packages/, but it is also
possible to extend the list of directories by setting the environment variable
\c XDG_DATA_DIRS. For instance adding /opt/myapp/share to \c XDG_DATA_DIRS will result
in /opt/myapp/share/mime/packages/ being searched for MIME definitions.
Here is an example of MIME XML:
\snippet code/src_corelib_mimetype_qmimedatabase.cpp 2
For more details about the syntax of XML MIME definitions, including defining
"magic" in order to detect MIME types based on data as well, read the
Shared Mime Info specification at
http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
On Unix systems, a binary cache is used for more performance. This cache is generated
by the command "update-mime-database path", where path would be /opt/myapp/share/mime
in the above example. Make sure to run this command when installing the MIME type
definition file.
\threadsafe
\snippet code/src_corelib_mimetype_qmimedatabase.cpp 0
\sa QMimeType, {MIME Type Browser Example}
*/
/*!
\fn QMimeDatabase::QMimeDatabase();
Constructs a QMimeDatabase object.
It is perfectly OK to create an instance of QMimeDatabase every time you need to
perform a lookup.
The parsing of mimetypes is done on demand (when shared-mime-info is installed)
or when the very first instance is constructed (when parsing XML files directly).
*/
QMimeDatabase::QMimeDatabase() :
d(staticQMimeDatabase())
{
}
/*!
\fn QMimeDatabase::~QMimeDatabase();
Destroys the QMimeDatabase object.
*/
QMimeDatabase::~QMimeDatabase()
{
d = nullptr;
}
/*!
\fn QMimeType QMimeDatabase::mimeTypeForName(const QString &nameOrAlias) const;
Returns a MIME type for \a nameOrAlias or an invalid one if none found.
*/
QMimeType QMimeDatabase::mimeTypeForName(const QString &nameOrAlias) const
{
QMutexLocker locker(&d->mutex);
return d->mimeTypeForName(nameOrAlias);
}
/*!
Returns a MIME type for \a fileInfo.
A valid MIME type is always returned.
The default matching algorithm looks at both the file name and the file
contents, if necessary. The file extension has priority over the contents,
but the contents will be used if the file extension is unknown, or
matches multiple MIME types.
If \a fileInfo is a Unix symbolic link, the file that it refers to
will be used instead.
If the file doesn't match any known pattern or data, the default MIME type
(application/octet-stream) is returned.
When \a mode is set to MatchExtension, only the file name is used, not
the file contents. The file doesn't even have to exist. If the file name
doesn't match any known pattern, the default MIME type (application/octet-stream)
is returned.
If multiple MIME types match this file, the first one (alphabetically) is returned.
When \a mode is set to MatchContent, and the file is readable, only the
file contents are used to determine the MIME type. This is equivalent to
calling mimeTypeForData with a QFile as input device.
\a fileInfo may refer to an absolute or relative path.
\sa QMimeType::isDefault(), mimeTypeForData()
*/
QMimeType QMimeDatabase::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const
{
QMutexLocker locker(&d->mutex);
if (fileInfo.isDir())
return d->mimeTypeForName(QLatin1String("inode/directory"));
const QString filePath = fileInfo.filePath();
#ifdef Q_OS_UNIX
// Cannot access statBuf.st_mode from the filesystem engine, so we have to stat again.
// In addition we want to follow symlinks.
const QByteArray nativeFilePath = QFile::encodeName(filePath);
QT_STATBUF statBuffer;
if (QT_STAT(nativeFilePath.constData(), &statBuffer) == 0) {
if (S_ISCHR(statBuffer.st_mode))
return d->mimeTypeForName(QLatin1String("inode/chardevice"));
if (S_ISBLK(statBuffer.st_mode))
return d->mimeTypeForName(QLatin1String("inode/blockdevice"));
if (S_ISFIFO(statBuffer.st_mode))
return d->mimeTypeForName(QLatin1String("inode/fifo"));
if (S_ISSOCK(statBuffer.st_mode))
return d->mimeTypeForName(QLatin1String("inode/socket"));
}
#endif
int priority = 0;
switch (mode) {
case MatchDefault:
return d->mimeTypeForFileNameAndData(filePath, nullptr, &priority);
case MatchExtension:
locker.unlock();
return mimeTypeForFile(filePath, mode);
case MatchContent: {
QFile file(filePath);
if (file.open(QIODevice::ReadOnly)) {
locker.unlock();
return mimeTypeForData(&file);
} else {
return d->mimeTypeForName(d->defaultMimeType());
}
}
default:
Q_ASSERT(false);
}
return d->mimeTypeForName(d->defaultMimeType());
}
/*!
Returns a MIME type for the file named \a fileName using \a mode.
\overload
*/
QMimeType QMimeDatabase::mimeTypeForFile(const QString &fileName, MatchMode mode) const
{
if (mode == MatchExtension) {
QMutexLocker locker(&d->mutex);
const QStringList matches = d->mimeTypeForFileName(fileName);
const int matchCount = matches.count();
if (matchCount == 0) {
return d->mimeTypeForName(d->defaultMimeType());
} else if (matchCount == 1) {
return d->mimeTypeForName(matches.first());
} else {
// We have to pick one.
return d->mimeTypeForName(matches.first());
}
} else {
// Implemented as a wrapper around mimeTypeForFile(QFileInfo), so no mutex.
QFileInfo fileInfo(fileName);
return mimeTypeForFile(fileInfo, mode);
}
}
/*!
Returns the MIME types for the file name \a fileName.
If the file name doesn't match any known pattern, an empty list is returned.
If multiple MIME types match this file, they are all returned.
This function does not try to open the file. To also use the content
when determining the MIME type, use mimeTypeForFile() or
mimeTypeForFileNameAndData() instead.
\sa mimeTypeForFile()
*/
QList<QMimeType> QMimeDatabase::mimeTypesForFileName(const QString &fileName) const
{
QMutexLocker locker(&d->mutex);
const QStringList matches = d->mimeTypeForFileName(fileName);
QList<QMimeType> mimes;
mimes.reserve(matches.count());
for (const QString &mime : matches)
mimes.append(d->mimeTypeForName(mime));
return mimes;
}
/*!
Returns the suffix for the file \a fileName, as known by the MIME database.
This allows to pre-select "tar.bz2" for foo.tar.bz2, but still only
"txt" for my.file.with.dots.txt.
*/
QString QMimeDatabase::suffixForFileName(const QString &fileName) const
{
QMutexLocker locker(&d->mutex);
const int suffixLength = d->findByFileName(fileName).m_knownSuffixLength;
return fileName.right(suffixLength);
}
/*!
Returns a MIME type for \a data.
A valid MIME type is always returned. If \a data doesn't match any
known MIME type data, the default MIME type (application/octet-stream)
is returned.
*/
QMimeType QMimeDatabase::mimeTypeForData(const QByteArray &data) const
{
QMutexLocker locker(&d->mutex);
int accuracy = 0;
return d->findByData(data, &accuracy);
}
/*!
Returns a MIME type for the data in \a device.
A valid MIME type is always returned. If the data in \a device doesn't match any
known MIME type data, the default MIME type (application/octet-stream)
is returned.
*/
QMimeType QMimeDatabase::mimeTypeForData(QIODevice *device) const
{
QMutexLocker locker(&d->mutex);
int accuracy = 0;
const bool openedByUs = !device->isOpen() && device->open(QIODevice::ReadOnly);
if (device->isOpen()) {
// Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h).
// This is much faster than seeking back and forth into QIODevice.
const QByteArray data = device->peek(16384);
const QMimeType result = d->findByData(data, &accuracy);
if (openedByUs)
device->close();
return result;
}
return d->mimeTypeForName(d->defaultMimeType());
}
/*!
Returns a MIME type for \a url.
If the URL is a local file, this calls mimeTypeForFile.
Otherwise the matching is done based on the file name only,
except for schemes where file names don't mean much, like HTTP.
This method always returns the default mimetype for HTTP URLs,
use QNetworkAccessManager to handle HTTP URLs properly.
A valid MIME type is always returned. If \a url doesn't match any
known MIME type data, the default MIME type (application/octet-stream)
is returned.
*/
QMimeType QMimeDatabase::mimeTypeForUrl(const QUrl &url) const
{
if (url.isLocalFile())
return mimeTypeForFile(url.toLocalFile());
const QString scheme = url.scheme();
if (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("mailto"))
return mimeTypeForName(d->defaultMimeType());
return mimeTypeForFile(url.path(), MatchExtension);
}
/*!
Returns a MIME type for the given \a fileName and \a device data.
This overload can be useful when the file is remote, and we started to
download some of its data in a device. This allows to do full MIME type
matching for remote files as well.
If the device is not open, it will be opened by this function, and closed
after the MIME type detection is completed.
A valid MIME type is always returned. If \a device data doesn't match any
known MIME type data, the default MIME type (application/octet-stream)
is returned.
This method looks at both the file name and the file contents,
if necessary. The file extension has priority over the contents,
but the contents will be used if the file extension is unknown, or
matches multiple MIME types.
*/
QMimeType QMimeDatabase::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const
{
QMutexLocker locker(&d->mutex);
if (fileName.endsWith(QLatin1Char('/')))
return d->mimeTypeForName(QLatin1String("inode/directory"));
int accuracy = 0;
const bool openedByUs = !device->isOpen() && device->open(QIODevice::ReadOnly);
const QMimeType result = d->mimeTypeForFileNameAndData(fileName, device, &accuracy);
if (openedByUs)
device->close();
return result;
}
/*!
Returns a MIME type for the given \a fileName and device \a data.
This overload can be useful when the file is remote, and we started to
download some of its data. This allows to do full MIME type matching for
remote files as well.
A valid MIME type is always returned. If \a data doesn't match any
known MIME type data, the default MIME type (application/octet-stream)
is returned.
This method looks at both the file name and the file contents,
if necessary. The file extension has priority over the contents,
but the contents will be used if the file extension is unknown, or
matches multiple MIME types.
*/
QMimeType QMimeDatabase::mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const
{
QMutexLocker locker(&d->mutex);
if (fileName.endsWith(QLatin1Char('/')))
return d->mimeTypeForName(QLatin1String("inode/directory"));
QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly);
int accuracy = 0;
return d->mimeTypeForFileNameAndData(fileName, &buffer, &accuracy);
}
/*!
Returns the list of all available MIME types.
This can be useful for showing all MIME types to the user, for instance
in a MIME type editor. Do not use unless really necessary in other cases
though, prefer using the \l {mimeTypeForData()}{mimeTypeForXxx()} methods for performance reasons.
*/
QList<QMimeType> QMimeDatabase::allMimeTypes() const
{
QMutexLocker locker(&d->mutex);
return d->allMimeTypes();
}
/*!
\enum QMimeDatabase::MatchMode
This enum specifies how matching a file to a MIME type is performed.
\value MatchDefault Both the file name and content are used to look for a match
\value MatchExtension Only the file name is used to look for a match
\value MatchContent The file content is used to look for a match
*/
QT_END_NAMESPACE

View File

@@ -0,0 +1,95 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEDATABASE_H
#define QMIMEDATABASE_H
#include <QtCore/qmimetype.h>
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qstringlist.h>
QT_BEGIN_NAMESPACE
class QByteArray;
class QFileInfo;
class QIODevice;
class QUrl;
class QMimeDatabasePrivate;
class Q_CORE_EXPORT QMimeDatabase
{
Q_DISABLE_COPY(QMimeDatabase)
public:
QMimeDatabase();
~QMimeDatabase();
QMimeType mimeTypeForName(const QString &nameOrAlias) const;
enum MatchMode {
MatchDefault = 0x0,
MatchExtension = 0x1,
MatchContent = 0x2
};
QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode = MatchDefault) const;
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode = MatchDefault) const;
QList<QMimeType> mimeTypesForFileName(const QString &fileName) const;
QMimeType mimeTypeForData(const QByteArray &data) const;
QMimeType mimeTypeForData(QIODevice *device) const;
QMimeType mimeTypeForUrl(const QUrl &url) const;
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const;
QMimeType mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const;
QString suffixForFileName(const QString &fileName) const;
QList<QMimeType> allMimeTypes() const;
private:
QMimeDatabasePrivate *d;
};
QT_END_NAMESPACE
#endif // QMIMEDATABASE_H

View File

@@ -0,0 +1,123 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEDATABASE_P_H
#define QMIMEDATABASE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qmimetype.h"
QT_REQUIRE_CONFIG(mimetype);
#include "qmimetype_p.h"
#include "qmimeglobpattern_p.h"
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <vector>
#include <memory>
QT_BEGIN_NAMESPACE
class QIODevice;
class QMimeDatabase;
class QMimeProviderBase;
class QMimeDatabasePrivate
{
public:
Q_DISABLE_COPY_MOVE(QMimeDatabasePrivate)
QMimeDatabasePrivate();
~QMimeDatabasePrivate();
static QMimeDatabasePrivate *instance();
inline QString defaultMimeType() const { return m_defaultMimeType; }
bool inherits(const QString &mime, const QString &parent);
QList<QMimeType> allMimeTypes();
QString resolveAlias(const QString &nameOrAlias);
QStringList parents(const QString &mimeName);
QMimeType mimeTypeForName(const QString &nameOrAlias);
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device, int *priorityPtr);
QMimeType findByData(const QByteArray &data, int *priorityPtr);
QStringList mimeTypeForFileName(const QString &fileName);
QMimeGlobMatchResult findByFileName(const QString &fileName);
// API for QMimeType. Takes care of locking the mutex.
void loadMimeTypePrivate(QMimeTypePrivate &mimePrivate);
void loadGenericIcon(QMimeTypePrivate &mimePrivate);
void loadIcon(QMimeTypePrivate &mimePrivate);
QStringList mimeParents(const QString &mimeName);
QStringList listAliases(const QString &mimeName);
bool mimeInherits(const QString &mime, const QString &parent);
private:
using Providers = std::vector<std::unique_ptr<QMimeProviderBase>>;
const Providers &providers();
bool shouldCheck();
void loadProviders();
mutable Providers m_providers;
QElapsedTimer m_lastCheck;
public:
const QString m_defaultMimeType;
QMutex mutex;
};
QT_END_NAMESPACE
#endif // QMIMEDATABASE_P_H

View File

@@ -0,0 +1,304 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmimeglobpattern_p.h"
#if QT_CONFIG(regularexpression)
#include <QRegularExpression>
#endif
#include <QStringList>
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\internal
\class QMimeGlobMatchResult
\inmodule QtCore
\brief The QMimeGlobMatchResult class accumulates results from glob matching.
Handles glob weights, and preferring longer matches over shorter matches.
*/
void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern, int knownSuffixLength)
{
if (m_allMatchingMimeTypes.contains(mimeType))
return;
// Is this a lower-weight pattern than the last match? Skip this match then.
if (weight < m_weight) {
m_allMatchingMimeTypes.append(mimeType);
return;
}
bool replace = weight > m_weight;
if (!replace) {
// Compare the length of the match
if (pattern.length() < m_matchingPatternLength)
return; // too short, ignore
else if (pattern.length() > m_matchingPatternLength) {
// longer: clear any previous match (like *.bz2, when pattern is *.tar.bz2)
replace = true;
}
}
if (replace) {
m_matchingMimeTypes.clear();
// remember the new "longer" length
m_matchingPatternLength = pattern.length();
m_weight = weight;
}
if (!m_matchingMimeTypes.contains(mimeType)) {
m_matchingMimeTypes.append(mimeType);
if (replace)
m_allMatchingMimeTypes.prepend(mimeType); // highest-weight first
else
m_allMatchingMimeTypes.append(mimeType);
m_knownSuffixLength = knownSuffixLength;
}
}
QMimeGlobPattern::PatternType QMimeGlobPattern::detectPatternType(const QString &pattern) const
{
const int patternLength = pattern.length();
if (!patternLength)
return OtherPattern;
const int starCount = pattern.count(QLatin1Char('*'));
const bool hasSquareBracket = pattern.indexOf(QLatin1Char('[')) != -1;
const bool hasQuestionMark = pattern.indexOf(QLatin1Char('?')) != -1;
if (!hasSquareBracket && !hasQuestionMark) {
if (starCount == 1) {
// Patterns like "*~", "*.extension"
if (pattern.at(0) == QLatin1Char('*'))
return SuffixPattern;
// Patterns like "README*" (well this is currently the only one like that...)
if (pattern.at(patternLength - 1) == QLatin1Char('*'))
return PrefixPattern;
} else if (starCount == 0) {
// Names without any wildcards like "README"
return LiteralPattern;
}
}
if (pattern == QLatin1String("[0-9][0-9][0-9].vdr"))
return VdrPattern;
if (pattern == QLatin1String("*.anim[1-9j]"))
return AnimPattern;
return OtherPattern;
}
/*!
\internal
\class QMimeGlobPattern
\inmodule QtCore
\brief The QMimeGlobPattern class contains the glob pattern for file names for MIME type matching.
\sa QMimeType, QMimeDatabase, QMimeMagicRuleMatcher, QMimeMagicRule
*/
bool QMimeGlobPattern::matchFileName(const QString &inputFileName) const
{
// "Applications MUST match globs case-insensitively, except when the case-sensitive
// attribute is set to true."
// The constructor takes care of putting case-insensitive patterns in lowercase.
const QString fileName = m_caseSensitivity == Qt::CaseInsensitive
? inputFileName.toLower() : inputFileName;
const int patternLength = m_pattern.length();
if (!patternLength)
return false;
const int fileNameLength = fileName.length();
switch (m_patternType) {
case SuffixPattern: {
if (fileNameLength + 1 < patternLength)
return false;
const QChar *c1 = m_pattern.unicode() + patternLength - 1;
const QChar *c2 = fileName.unicode() + fileNameLength - 1;
int cnt = 1;
while (cnt < patternLength && *c1-- == *c2--)
++cnt;
return cnt == patternLength;
}
case PrefixPattern: {
if (fileNameLength + 1 < patternLength)
return false;
const QChar *c1 = m_pattern.unicode();
const QChar *c2 = fileName.unicode();
int cnt = 1;
while (cnt < patternLength && *c1++ == *c2++)
++cnt;
return cnt == patternLength;
}
case LiteralPattern:
return (m_pattern == fileName);
case VdrPattern: // "[0-9][0-9][0-9].vdr" case
return fileNameLength == 7
&& fileName.at(0).isDigit() && fileName.at(1).isDigit() && fileName.at(2).isDigit()
&& QStringView{fileName}.mid(3, 4) == QLatin1String(".vdr");
case AnimPattern: { // "*.anim[1-9j]" case
if (fileNameLength < 6)
return false;
const QChar lastChar = fileName.at(fileNameLength - 1);
const bool lastCharOK = (lastChar.isDigit() && lastChar != QLatin1Char('0'))
|| lastChar == QLatin1Char('j');
return lastCharOK && QStringView{fileName}.mid(fileNameLength - 6, 5) == QLatin1String(".anim");
}
case OtherPattern:
// Other fallback patterns: slow but correct method
#if QT_CONFIG(regularexpression)
auto rx = QRegularExpression::fromWildcard(m_pattern);
return rx.match(fileName).hasMatch();
#else
return false;
#endif
}
return false;
}
static bool isSimplePattern(const QString &pattern)
{
// starts with "*.", has no other '*'
return pattern.lastIndexOf(QLatin1Char('*')) == 0
&& pattern.length() > 1
&& pattern.at(1) == QLatin1Char('.') // (other dots are OK, like *.tar.bz2)
// and contains no other special character
&& !pattern.contains(QLatin1Char('?'))
&& !pattern.contains(QLatin1Char('['))
;
}
static bool isFastPattern(const QString &pattern)
{
// starts with "*.", has no other '*' and no other '.'
return pattern.lastIndexOf(QLatin1Char('*')) == 0
&& pattern.lastIndexOf(QLatin1Char('.')) == 1
// and contains no other special character
&& !pattern.contains(QLatin1Char('?'))
&& !pattern.contains(QLatin1Char('['))
;
}
void QMimeAllGlobPatterns::addGlob(const QMimeGlobPattern &glob)
{
const QString &pattern = glob.pattern();
Q_ASSERT(!pattern.isEmpty());
// Store each patterns into either m_fastPatternDict (*.txt, *.html etc. with default weight 50)
// or for the rest, like core.*, *.tar.bz2, *~, into highWeightPatternOffset (>50)
// or lowWeightPatternOffset (<=50)
if (glob.weight() == 50 && isFastPattern(pattern) && !glob.isCaseSensitive()) {
// The bulk of the patterns is *.foo with weight 50 --> those go into the fast patterns hash.
const QString extension = pattern.mid(2).toLower();
QStringList &patterns = m_fastPatterns[extension]; // find or create
if (!patterns.contains(glob.mimeType()))
patterns.append(glob.mimeType());
} else {
if (glob.weight() > 50) {
if (!m_highWeightGlobs.hasPattern(glob.mimeType(), glob.pattern()))
m_highWeightGlobs.append(glob);
} else {
if (!m_lowWeightGlobs.hasPattern(glob.mimeType(), glob.pattern()))
m_lowWeightGlobs.append(glob);
}
}
}
void QMimeAllGlobPatterns::removeMimeType(const QString &mimeType)
{
for (auto &x : m_fastPatterns)
x.removeAll(mimeType);
m_highWeightGlobs.removeMimeType(mimeType);
m_lowWeightGlobs.removeMimeType(mimeType);
}
void QMimeGlobPatternList::match(QMimeGlobMatchResult &result,
const QString &fileName) const
{
QMimeGlobPatternList::const_iterator it = this->constBegin();
const QMimeGlobPatternList::const_iterator endIt = this->constEnd();
for (; it != endIt; ++it) {
const QMimeGlobPattern &glob = *it;
if (glob.matchFileName(fileName)) {
const QString pattern = glob.pattern();
const int suffixLen = isSimplePattern(pattern) ? pattern.length() - 2 : 0;
result.addMatch(glob.mimeType(), glob.weight(), pattern, suffixLen);
}
}
}
void QMimeAllGlobPatterns::matchingGlobs(const QString &fileName, QMimeGlobMatchResult &result) const
{
// First try the high weight matches (>50), if any.
m_highWeightGlobs.match(result, fileName);
// Now use the "fast patterns" dict, for simple *.foo patterns with weight 50
// (which is most of them, so this optimization is definitely worth it)
const int lastDot = fileName.lastIndexOf(QLatin1Char('.'));
if (lastDot != -1) { // if no '.', skip the extension lookup
const int ext_len = fileName.length() - lastDot - 1;
const QString simpleExtension = fileName.right(ext_len).toLower();
// (toLower because fast patterns are always case-insensitive and saved as lowercase)
const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension);
const QString simplePattern = QLatin1String("*.") + simpleExtension;
for (const QString &mime : matchingMimeTypes)
result.addMatch(mime, 50, simplePattern, simpleExtension.size());
// Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the low-weight mimetypes anyway,
// at least those with weight 50.
}
// Finally, try the low weight matches (<=50)
m_lowWeightGlobs.match(result, fileName);
}
void QMimeAllGlobPatterns::clear()
{
m_fastPatterns.clear();
m_highWeightGlobs.clear();
m_lowWeightGlobs.clear();
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,176 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEGLOBPATTERN_P_H
#define QMIMEGLOBPATTERN_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/private/qglobal_p.h>
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qstringlist.h>
#include <QtCore/qhash.h>
QT_BEGIN_NAMESPACE
struct QMimeGlobMatchResult
{
void addMatch(const QString &mimeType, int weight, const QString &pattern, int knownSuffixLength = 0);
QStringList m_matchingMimeTypes; // only those with highest weight
QStringList m_allMatchingMimeTypes;
int m_weight = 0;
int m_matchingPatternLength = 0;
int m_knownSuffixLength = 0;
};
class QMimeGlobPattern
{
public:
static const unsigned MaxWeight = 100;
static const unsigned DefaultWeight = 50;
static const unsigned MinWeight = 1;
explicit QMimeGlobPattern(const QString &thePattern, const QString &theMimeType, unsigned theWeight = DefaultWeight, Qt::CaseSensitivity s = Qt::CaseInsensitive) :
m_pattern(s == Qt::CaseInsensitive ? thePattern.toLower() : thePattern),
m_mimeType(theMimeType),
m_weight(theWeight),
m_caseSensitivity(s),
m_patternType(detectPatternType(m_pattern))
{
}
void swap(QMimeGlobPattern &other) noexcept
{
qSwap(m_pattern, other.m_pattern);
qSwap(m_mimeType, other.m_mimeType);
qSwap(m_weight, other.m_weight);
qSwap(m_caseSensitivity, other.m_caseSensitivity);
qSwap(m_patternType, other.m_patternType);
}
bool matchFileName(const QString &inputFileName) const;
inline const QString &pattern() const { return m_pattern; }
inline unsigned weight() const { return m_weight; }
inline const QString &mimeType() const { return m_mimeType; }
inline bool isCaseSensitive() const { return m_caseSensitivity == Qt::CaseSensitive; }
private:
enum PatternType {
SuffixPattern,
PrefixPattern,
LiteralPattern,
VdrPattern, // special handling for "[0-9][0-9][0-9].vdr" pattern
AnimPattern, // special handling for "*.anim[1-9j]" pattern
OtherPattern
};
PatternType detectPatternType(const QString &pattern) const;
QString m_pattern;
QString m_mimeType;
int m_weight;
Qt::CaseSensitivity m_caseSensitivity;
PatternType m_patternType;
};
Q_DECLARE_SHARED(QMimeGlobPattern)
class QMimeGlobPatternList : public QList<QMimeGlobPattern>
{
public:
bool hasPattern(const QString &mimeType, const QString &pattern) const
{
const_iterator it = begin();
const const_iterator myend = end();
for (; it != myend; ++it)
if ((*it).pattern() == pattern && (*it).mimeType() == mimeType)
return true;
return false;
}
/*!
"noglobs" is very rare occurrence, so it's ok if it's slow
*/
void removeMimeType(const QString &mimeType)
{
auto isMimeTypeEqual = [&mimeType](const QMimeGlobPattern &pattern) {
return pattern.mimeType() == mimeType;
};
removeIf(isMimeTypeEqual);
}
void match(QMimeGlobMatchResult &result, const QString &fileName) const;
};
/*!
Result of the globs parsing, as data structures ready for efficient MIME type matching.
This contains:
1) a map of fast regular patterns (e.g. *.txt is stored as "txt" in a qhash's key)
2) a linear list of high-weight globs
3) a linear list of low-weight globs
*/
class QMimeAllGlobPatterns
{
public:
typedef QHash<QString, QStringList> PatternsMap; // MIME type -> patterns
void addGlob(const QMimeGlobPattern &glob);
void removeMimeType(const QString &mimeType);
void matchingGlobs(const QString &fileName, QMimeGlobMatchResult &result) const;
void clear();
PatternsMap m_fastPatterns; // example: "doc" -> "application/msword", "text/plain"
QMimeGlobPatternList m_highWeightGlobs;
QMimeGlobPatternList m_lowWeightGlobs; // <= 50, including the non-fast 50 patterns
};
QT_END_NAMESPACE
#endif // QMIMEGLOBPATTERN_P_H

View File

@@ -0,0 +1,364 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#define QT_NO_CAST_FROM_ASCII
#include "qmimemagicrule_p.h"
#include "qmimetypeparser_p.h"
#include <QtCore/QList>
#include <QtCore/QDebug>
#include <qendian.h>
QT_BEGIN_NAMESPACE
// in the same order as Type!
static const char magicRuleTypes_string[] =
"invalid\0"
"string\0"
"host16\0"
"host32\0"
"big16\0"
"big32\0"
"little16\0"
"little32\0"
"byte\0"
"\0";
static const int magicRuleTypes_indices[] = {
0, 8, 15, 22, 29, 35, 41, 50, 59, 64, 0
};
QMimeMagicRule::Type QMimeMagicRule::type(const QByteArray &theTypeName)
{
for (int i = String; i <= Byte; ++i) {
if (theTypeName == magicRuleTypes_string + magicRuleTypes_indices[i])
return Type(i);
}
return Invalid;
}
QByteArray QMimeMagicRule::typeName(QMimeMagicRule::Type theType)
{
return magicRuleTypes_string + magicRuleTypes_indices[theType];
}
bool QMimeMagicRule::operator==(const QMimeMagicRule &other) const
{
return m_type == other.m_type &&
m_value == other.m_value &&
m_startPos == other.m_startPos &&
m_endPos == other.m_endPos &&
m_mask == other.m_mask &&
m_pattern == other.m_pattern &&
m_number == other.m_number &&
m_numberMask == other.m_numberMask &&
m_matchFunction == other.m_matchFunction;
}
// Used by both providers
bool QMimeMagicRule::matchSubstring(const char *dataPtr, int dataSize, int rangeStart, int rangeLength,
int valueLength, const char *valueData, const char *mask)
{
// Size of searched data.
// Example: value="ABC", rangeLength=3 -> we need 3+3-1=5 bytes (ABCxx,xABCx,xxABC would match)
const int dataNeeded = qMin(rangeLength + valueLength - 1, dataSize - rangeStart);
if (!mask) {
// callgrind says QByteArray::indexOf is much slower, since our strings are typically too
// short for be worth Boyer-Moore matching (1 to 71 bytes, 11 bytes on average).
bool found = false;
for (int i = rangeStart; i < rangeStart + rangeLength; ++i) {
if (i + valueLength > dataSize)
break;
if (memcmp(valueData, dataPtr + i, valueLength) == 0) {
found = true;
break;
}
}
if (!found)
return false;
} else {
bool found = false;
const char *readDataBase = dataPtr + rangeStart;
// Example (continued from above):
// deviceSize is 4, so dataNeeded was max'ed to 4.
// maxStartPos = 4 - 3 + 1 = 2, and indeed
// we need to check for a match a positions 0 and 1 (ABCx and xABC).
const int maxStartPos = dataNeeded - valueLength + 1;
for (int i = 0; i < maxStartPos; ++i) {
const char *d = readDataBase + i;
bool valid = true;
for (int idx = 0; idx < valueLength; ++idx) {
if (((*d++) & mask[idx]) != (valueData[idx] & mask[idx])) {
valid = false;
break;
}
}
if (valid)
found = true;
}
if (!found)
return false;
}
//qDebug() << "Found" << value << "in" << searchedData;
return true;
}
bool QMimeMagicRule::matchString(const QByteArray &data) const
{
const int rangeLength = m_endPos - m_startPos + 1;
return QMimeMagicRule::matchSubstring(data.constData(), data.size(), m_startPos, rangeLength, m_pattern.size(), m_pattern.constData(), m_mask.constData());
}
template <typename T>
bool QMimeMagicRule::matchNumber(const QByteArray &data) const
{
const T value(m_number);
const T mask(m_numberMask);
//qDebug() << "matchNumber" << "0x" << QString::number(m_number, 16) << "size" << sizeof(T);
//qDebug() << "mask" << QString::number(m_numberMask, 16);
const char *p = data.constData() + m_startPos;
const char *e = data.constData() + qMin(data.size() - int(sizeof(T)), m_endPos);
for ( ; p <= e; ++p) {
if ((qFromUnaligned<T>(p) & mask) == (value & mask))
return true;
}
return false;
}
static inline QByteArray makePattern(const QByteArray &value)
{
QByteArray pattern(value.size(), Qt::Uninitialized);
char *data = pattern.data();
const char *p = value.constData();
const char *e = p + value.size();
for ( ; p < e; ++p) {
if (*p == '\\' && ++p < e) {
if (*p == 'x') { // hex (\\xff)
char c = 0;
for (int i = 0; i < 2 && p + 1 < e; ++i) {
++p;
if (*p >= '0' && *p <= '9')
c = (c << 4) + *p - '0';
else if (*p >= 'a' && *p <= 'f')
c = (c << 4) + *p - 'a' + 10;
else if (*p >= 'A' && *p <= 'F')
c = (c << 4) + *p - 'A' + 10;
else
continue;
}
*data++ = c;
} else if (*p >= '0' && *p <= '7') { // oct (\\7, or \\77, or \\377)
char c = *p - '0';
if (p + 1 < e && p[1] >= '0' && p[1] <= '7') {
c = (c << 3) + *(++p) - '0';
if (p + 1 < e && p[1] >= '0' && p[1] <= '7' && p[-1] <= '3')
c = (c << 3) + *(++p) - '0';
}
*data++ = c;
} else if (*p == 'n') {
*data++ = '\n';
} else if (*p == 'r') {
*data++ = '\r';
} else if (*p == 't') {
*data++ = '\t';
} else { // escaped
*data++ = *p;
}
} else {
*data++ = *p;
}
}
pattern.truncate(data - pattern.data());
return pattern;
}
// Evaluate a magic match rule like
// <match value="must be converted with BinHex" type="string" offset="11"/>
// <match value="0x9501" type="big16" offset="0:64"/>
QMimeMagicRule::QMimeMagicRule(const QString &type,
const QByteArray &value,
const QString &offsets,
const QByteArray &mask,
QString *errorString)
: m_type(QMimeMagicRule::type(type.toLatin1())),
m_value(value),
m_mask(mask),
m_matchFunction(nullptr)
{
if (Q_UNLIKELY(m_type == Invalid))
*errorString = QLatin1String("Type ") + type + QLatin1String(" is not supported");
// Parse for offset as "1" or "1:10"
const int colonIndex = offsets.indexOf(QLatin1Char(':'));
const QStringView startPosStr = QStringView{offsets}.mid(0, colonIndex); // \ These decay to returning 'offsets'
const QStringView endPosStr = QStringView{offsets}.mid(colonIndex + 1);// / unchanged when colonIndex == -1
if (Q_UNLIKELY(!QMimeTypeParserBase::parseNumber(startPosStr, &m_startPos, errorString)) ||
Q_UNLIKELY(!QMimeTypeParserBase::parseNumber(endPosStr, &m_endPos, errorString))) {
m_type = Invalid;
return;
}
if (Q_UNLIKELY(m_value.isEmpty())) {
m_type = Invalid;
if (errorString)
*errorString = QStringLiteral("Invalid empty magic rule value");
return;
}
if (m_type >= Host16 && m_type <= Byte) {
bool ok;
m_number = m_value.toUInt(&ok, 0); // autodetect base
if (Q_UNLIKELY(!ok)) {
m_type = Invalid;
if (errorString)
*errorString = QLatin1String("Invalid magic rule value \"") + QLatin1String(m_value) + QLatin1Char('"');
return;
}
m_numberMask = !m_mask.isEmpty() ? m_mask.toUInt(&ok, 0) : 0; // autodetect base
}
switch (m_type) {
case String:
m_pattern = makePattern(m_value);
m_pattern.squeeze();
if (!m_mask.isEmpty()) {
if (Q_UNLIKELY(m_mask.size() < 4 || !m_mask.startsWith("0x"))) {
m_type = Invalid;
if (errorString)
*errorString = QLatin1String("Invalid magic rule mask \"") + QLatin1String(m_mask) + QLatin1Char('"');
return;
}
const QByteArray &tempMask = QByteArray::fromHex(QByteArray::fromRawData(
m_mask.constData() + 2, m_mask.size() - 2));
if (Q_UNLIKELY(tempMask.size() != m_pattern.size())) {
m_type = Invalid;
if (errorString)
*errorString = QLatin1String("Invalid magic rule mask size \"") + QLatin1String(m_mask) + QLatin1Char('"');
return;
}
m_mask = tempMask;
} else {
m_mask.fill(char(-1), m_pattern.size());
}
m_mask.squeeze();
m_matchFunction = &QMimeMagicRule::matchString;
break;
case Byte:
if (m_number <= quint8(-1)) {
if (m_numberMask == 0)
m_numberMask = quint8(-1);
m_matchFunction = &QMimeMagicRule::matchNumber<quint8>;
}
break;
case Big16:
case Little16:
if (m_number <= quint16(-1)) {
m_number = m_type == Little16 ? qFromLittleEndian<quint16>(m_number) : qFromBigEndian<quint16>(m_number);
if (m_numberMask != 0)
m_numberMask = m_type == Little16 ? qFromLittleEndian<quint16>(m_numberMask) : qFromBigEndian<quint16>(m_numberMask);
}
Q_FALLTHROUGH();
case Host16:
if (m_number <= quint16(-1)) {
if (m_numberMask == 0)
m_numberMask = quint16(-1);
m_matchFunction = &QMimeMagicRule::matchNumber<quint16>;
}
break;
case Big32:
case Little32:
m_number = m_type == Little32 ? qFromLittleEndian<quint32>(m_number) : qFromBigEndian<quint32>(m_number);
if (m_numberMask != 0)
m_numberMask = m_type == Little32 ? qFromLittleEndian<quint32>(m_numberMask) : qFromBigEndian<quint32>(m_numberMask);
Q_FALLTHROUGH();
case Host32:
if (m_numberMask == 0)
m_numberMask = quint32(-1);
m_matchFunction = &QMimeMagicRule::matchNumber<quint32>;
break;
default:
break;
}
}
QByteArray QMimeMagicRule::mask() const
{
QByteArray result = m_mask;
if (m_type == String) {
// restore '0x'
result = "0x" + result.toHex();
}
return result;
}
bool QMimeMagicRule::matches(const QByteArray &data) const
{
const bool ok = m_matchFunction && (this->*m_matchFunction)(data);
if (!ok)
return false;
// No submatch? Then we are done.
if (m_subMatches.isEmpty())
return true;
//qDebug() << "Checking" << m_subMatches.count() << "sub-rules";
// Check that one of the submatches matches too
for ( QList<QMimeMagicRule>::const_iterator it = m_subMatches.begin(), end = m_subMatches.end() ;
it != end ; ++it ) {
if ((*it).matches(data)) {
// One of the hierarchies matched -> mimetype recognized.
return true;
}
}
return false;
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,128 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEMAGICRULE_P_H
#define QMIMEMAGICRULE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/private/qglobal_p.h>
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qbytearray.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qlist.h>
QT_BEGIN_NAMESPACE
class QMimeMagicRule
{
public:
enum Type { Invalid = 0, String, Host16, Host32, Big16, Big32, Little16, Little32, Byte };
QMimeMagicRule(const QString &typeStr, const QByteArray &value, const QString &offsets,
const QByteArray &mask, QString *errorString);
void swap(QMimeMagicRule &other) noexcept
{
qSwap(m_type, other.m_type);
qSwap(m_value, other.m_value);
qSwap(m_startPos, other.m_startPos);
qSwap(m_endPos, other.m_endPos);
qSwap(m_mask, other.m_mask);
qSwap(m_pattern, other.m_pattern);
qSwap(m_number, other.m_number);
qSwap(m_numberMask, other.m_numberMask);
qSwap(m_matchFunction, other.m_matchFunction);
}
bool operator==(const QMimeMagicRule &other) const;
Type type() const { return m_type; }
QByteArray value() const { return m_value; }
int startPos() const { return m_startPos; }
int endPos() const { return m_endPos; }
QByteArray mask() const;
bool isValid() const { return m_matchFunction != nullptr; }
bool matches(const QByteArray &data) const;
QList<QMimeMagicRule> m_subMatches;
static Type type(const QByteArray &type);
static QByteArray typeName(Type type);
static bool matchSubstring(const char *dataPtr, int dataSize, int rangeStart, int rangeLength, int valueLength, const char *valueData, const char *mask);
private:
Type m_type;
QByteArray m_value;
int m_startPos;
int m_endPos;
QByteArray m_mask;
QByteArray m_pattern;
quint32 m_number;
quint32 m_numberMask;
typedef bool (QMimeMagicRule::*MatchFunction)(const QByteArray &data) const;
MatchFunction m_matchFunction;
private:
// match functions
bool matchString(const QByteArray &data) const;
template <typename T>
bool matchNumber(const QByteArray &data) const;
};
Q_DECLARE_SHARED(QMimeMagicRule)
QT_END_NAMESPACE
#endif // QMIMEMAGICRULE_H

View File

@@ -0,0 +1,106 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#define QT_NO_CAST_FROM_ASCII
#include "qmimemagicrulematcher_p.h"
#include "qmimetype_p.h"
QT_BEGIN_NAMESPACE
/*!
\internal
\class QMimeMagicRuleMatcher
\inmodule QtCore
\brief The QMimeMagicRuleMatcher class checks a number of rules based on operator "or".
It is used for rules parsed from XML files.
\sa QMimeType, QMimeDatabase, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
\sa QMimeTypeParserBase, MimeTypeParser
*/
QMimeMagicRuleMatcher::QMimeMagicRuleMatcher(const QString &mime, unsigned thePriority) :
m_list(),
m_priority(thePriority),
m_mimetype(mime)
{
}
bool QMimeMagicRuleMatcher::operator==(const QMimeMagicRuleMatcher &other) const
{
return m_list == other.m_list &&
m_priority == other.m_priority;
}
void QMimeMagicRuleMatcher::addRule(const QMimeMagicRule &rule)
{
m_list.append(rule);
}
void QMimeMagicRuleMatcher::addRules(const QList<QMimeMagicRule> &rules)
{
m_list.append(rules);
}
QList<QMimeMagicRule> QMimeMagicRuleMatcher::magicRules() const
{
return m_list;
}
// Check for a match on contents of a file
bool QMimeMagicRuleMatcher::matches(const QByteArray &data) const
{
for (const QMimeMagicRule &magicRule : m_list) {
if (magicRule.matches(data))
return true;
}
return false;
}
// Return a priority value from 1..100
unsigned QMimeMagicRuleMatcher::priority() const
{
return m_priority;
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,97 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEMAGICRULEMATCHER_P_H
#define QMIMEMAGICRULEMATCHER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qmimemagicrule_p.h"
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qbytearray.h>
#include <QtCore/qlist.h>
#include <QtCore/qstring.h>
QT_BEGIN_NAMESPACE
class QMimeMagicRuleMatcher
{
public:
explicit QMimeMagicRuleMatcher(const QString &mime, unsigned priority = 65535);
void swap(QMimeMagicRuleMatcher &other) noexcept
{
qSwap(m_list, other.m_list);
qSwap(m_priority, other.m_priority);
qSwap(m_mimetype, other.m_mimetype);
}
bool operator==(const QMimeMagicRuleMatcher &other) const;
void addRule(const QMimeMagicRule &rule);
void addRules(const QList<QMimeMagicRule> &rules);
QList<QMimeMagicRule> magicRules() const;
bool matches(const QByteArray &data) const;
unsigned priority() const;
QString mimetype() const { return m_mimetype; }
private:
QList<QMimeMagicRule> m_list;
unsigned m_priority;
QString m_mimetype;
};
Q_DECLARE_SHARED(QMimeMagicRuleMatcher)
QT_END_NAMESPACE
#endif // QMIMEMAGICRULEMATCHER_P_H

View File

@@ -0,0 +1,855 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Copyright (C) 2018 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Copyright (C) 2019 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmimeprovider_p.h"
#include "qmimetypeparser_p.h"
#include <qstandardpaths.h>
#include "qmimemagicrulematcher_p.h"
#include <QXmlStreamReader>
#include <QBuffer>
#include <QDir>
#include <QFile>
#include <QByteArrayMatcher>
#include <QDebug>
#include <QDateTime>
#include <QtEndian>
#if QT_CONFIG(mimetype_database)
# if defined(Q_CC_MSVC)
# pragma section(".qtmimedatabase", read, shared)
__declspec(allocate(".qtmimedatabase")) __declspec(align(4096))
# elif defined(Q_OS_DARWIN)
__attribute__((section("__TEXT,.qtmimedatabase"), aligned(4096)))
# elif (defined(Q_OF_ELF) || defined(Q_OS_WIN)) && defined(Q_CC_GNU)
__attribute__((section(".qtmimedatabase"), aligned(4096)))
# endif
# include "qmimeprovider_database.cpp"
# ifdef MIME_DATABASE_IS_ZSTD
# if !QT_CONFIG(zstd)
# error "MIME database is zstd but no support compiled in!"
# endif
# include <zstd.h>
# endif
# ifdef MIME_DATABASE_IS_GZIP
# ifdef QT_NO_COMPRESS
# error "MIME database is zlib but no support compiled in!"
# endif
# define ZLIB_CONST
# include <zconf.h>
# include <zlib.h>
# endif
#endif
QT_BEGIN_NAMESPACE
QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory)
: m_db(db), m_directory(directory)
{
}
QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory)
: QMimeProviderBase(db, directory), m_mimetypeListLoaded(false)
{
ensureLoaded();
}
struct QMimeBinaryProvider::CacheFile
{
CacheFile(const QString &fileName);
~CacheFile();
bool isValid() const { return m_valid; }
inline quint16 getUint16(int offset) const
{
return qFromBigEndian(*reinterpret_cast<quint16 *>(data + offset));
}
inline quint32 getUint32(int offset) const
{
return qFromBigEndian(*reinterpret_cast<quint32 *>(data + offset));
}
inline const char *getCharStar(int offset) const
{
return reinterpret_cast<const char *>(data + offset);
}
bool load();
bool reload();
QFile file;
uchar *data;
QDateTime m_mtime;
bool m_valid;
};
QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName)
: file(fileName), m_valid(false)
{
load();
}
QMimeBinaryProvider::CacheFile::~CacheFile()
{
}
bool QMimeBinaryProvider::CacheFile::load()
{
if (!file.open(QIODevice::ReadOnly))
return false;
data = file.map(0, file.size());
if (data) {
const int major = getUint16(0);
const int minor = getUint16(2);
m_valid = (major == 1 && minor >= 1 && minor <= 2);
}
m_mtime = QFileInfo(file).lastModified();
return m_valid;
}
bool QMimeBinaryProvider::CacheFile::reload()
{
m_valid = false;
if (file.isOpen()) {
file.close();
}
data = nullptr;
return load();
}
QMimeBinaryProvider::~QMimeBinaryProvider()
{
delete m_cacheFile;
}
bool QMimeBinaryProvider::isValid()
{
return m_cacheFile != nullptr;
}
bool QMimeBinaryProvider::isInternalDatabase() const
{
return false;
}
// Position of the "list offsets" values, at the beginning of the mime.cache file
enum {
PosAliasListOffset = 4,
PosParentListOffset = 8,
PosLiteralListOffset = 12,
PosReverseSuffixTreeOffset = 16,
PosGlobListOffset = 20,
PosMagicListOffset = 24,
// PosNamespaceListOffset = 28,
PosIconsListOffset = 32,
PosGenericIconsListOffset = 36
};
bool QMimeBinaryProvider::checkCacheChanged()
{
QFileInfo fileInfo(m_cacheFile->file);
if (fileInfo.lastModified() > m_cacheFile->m_mtime) {
// Deletion can't happen by just running update-mime-database.
// But the user could use rm -rf :-)
m_cacheFile->reload(); // will mark itself as invalid on failure
return true;
}
return false;
}
void QMimeBinaryProvider::ensureLoaded()
{
if (!m_cacheFile) {
const QString cacheFileName = m_directory + QLatin1String("/mime.cache");
m_cacheFile = new CacheFile(cacheFileName);
m_mimetypeListLoaded = false;
m_mimetypeExtra.clear();
} else {
if (checkCacheChanged()) {
m_mimetypeListLoaded = false;
m_mimetypeExtra.clear();
} else {
return; // nothing to do
}
}
if (!m_cacheFile->isValid()) { // verify existence and version
delete m_cacheFile;
m_cacheFile = nullptr;
}
}
static QMimeType mimeTypeForNameUnchecked(const QString &name)
{
QMimeTypePrivate data;
data.name = name;
data.fromCache = true;
// The rest is retrieved on demand.
// comment and globPatterns: in loadMimeTypePrivate
// iconName: in loadIcon
// genericIconName: in loadGenericIcon
return QMimeType(data);
}
QMimeType QMimeBinaryProvider::mimeTypeForName(const QString &name)
{
if (!m_mimetypeListLoaded)
loadMimeTypeList();
if (!m_mimetypeNames.contains(name))
return QMimeType(); // unknown mimetype
return mimeTypeForNameUnchecked(name);
}
void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
{
if (fileName.isEmpty())
return;
Q_ASSERT(m_cacheFile);
const QString lowerFileName = fileName.toLower();
// Check literals (e.g. "Makefile")
matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName);
// Check the very common *.txt cases with the suffix tree
if (result.m_matchingMimeTypes.isEmpty()) {
const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset);
const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset);
const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4);
matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.length() - 1, false);
if (result.m_matchingMimeTypes.isEmpty())
matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true);
}
// Check complex globs (e.g. "callgrind.out[0-9]*" or "README*")
if (result.m_matchingMimeTypes.isEmpty())
matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName);
}
void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName)
{
const int numGlobs = cacheFile->getUint32(off);
//qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset;
for (int i = 0; i < numGlobs; ++i) {
const int globOffset = cacheFile->getUint32(off + 4 + 12 * i);
const int mimeTypeOffset = cacheFile->getUint32(off + 4 + 12 * i + 4);
const int flagsAndWeight = cacheFile->getUint32(off + 4 + 12 * i + 8);
const int weight = flagsAndWeight & 0xff;
const bool caseSensitive = flagsAndWeight & 0x100;
const Qt::CaseSensitivity qtCaseSensitive = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
const QString pattern = QLatin1String(cacheFile->getCharStar(globOffset));
const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
//qDebug() << pattern << mimeType << weight << caseSensitive;
QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive);
if (glob.matchFileName(fileName))
result.addMatch(QLatin1String(mimeType), weight, pattern);
}
}
bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, QMimeBinaryProvider::CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck)
{
QChar fileChar = fileName[charPos];
int min = 0;
int max = numEntries - 1;
while (min <= max) {
const int mid = (min + max) / 2;
const int off = firstOffset + 12 * mid;
const QChar ch = char16_t(cacheFile->getUint32(off));
if (ch < fileChar)
min = mid + 1;
else if (ch > fileChar)
max = mid - 1;
else {
--charPos;
int numChildren = cacheFile->getUint32(off + 4);
int childrenOffset = cacheFile->getUint32(off + 8);
bool success = false;
if (charPos > 0)
success = matchSuffixTree(result, cacheFile, numChildren, childrenOffset, fileName, charPos, caseSensitiveCheck);
if (!success) {
for (int i = 0; i < numChildren; ++i) {
const int childOff = childrenOffset + 12 * i;
const int mch = cacheFile->getUint32(childOff);
if (mch != 0)
break;
const int mimeTypeOffset = cacheFile->getUint32(childOff + 4);
const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
const int flagsAndWeight = cacheFile->getUint32(childOff + 8);
const int weight = flagsAndWeight & 0xff;
const bool caseSensitive = flagsAndWeight & 0x100;
if (caseSensitiveCheck || !caseSensitive) {
result.addMatch(QLatin1String(mimeType), weight,
QLatin1Char('*') + QStringView{fileName}.mid(charPos + 1), fileName.size() - charPos - 2);
success = true;
}
}
}
return success;
}
}
return false;
}
bool QMimeBinaryProvider::matchMagicRule(QMimeBinaryProvider::CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data)
{
const char *dataPtr = data.constData();
const int dataSize = data.size();
for (int matchlet = 0; matchlet < numMatchlets; ++matchlet) {
const int off = firstOffset + matchlet * 32;
const int rangeStart = cacheFile->getUint32(off);
const int rangeLength = cacheFile->getUint32(off + 4);
//const int wordSize = cacheFile->getUint32(off + 8);
const int valueLength = cacheFile->getUint32(off + 12);
const int valueOffset = cacheFile->getUint32(off + 16);
const int maskOffset = cacheFile->getUint32(off + 20);
const char *mask = maskOffset ? cacheFile->getCharStar(maskOffset) : nullptr;
if (!QMimeMagicRule::matchSubstring(dataPtr, dataSize, rangeStart, rangeLength, valueLength, cacheFile->getCharStar(valueOffset), mask))
continue;
const int numChildren = cacheFile->getUint32(off + 24);
const int firstChildOffset = cacheFile->getUint32(off + 28);
if (numChildren == 0) // No submatch? Then we are done.
return true;
// Check that one of the submatches matches too
if (matchMagicRule(cacheFile, numChildren, firstChildOffset, data))
return true;
}
return false;
}
void QMimeBinaryProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate)
{
const int magicListOffset = m_cacheFile->getUint32(PosMagicListOffset);
const int numMatches = m_cacheFile->getUint32(magicListOffset);
//const int maxExtent = cacheFile->getUint32(magicListOffset + 4);
const int firstMatchOffset = m_cacheFile->getUint32(magicListOffset + 8);
for (int i = 0; i < numMatches; ++i) {
const int off = firstMatchOffset + i * 16;
const int numMatchlets = m_cacheFile->getUint32(off + 8);
const int firstMatchletOffset = m_cacheFile->getUint32(off + 12);
if (matchMagicRule(m_cacheFile, numMatchlets, firstMatchletOffset, data)) {
const int mimeTypeOffset = m_cacheFile->getUint32(off + 4);
const char *mimeType = m_cacheFile->getCharStar(mimeTypeOffset);
*accuracyPtr = m_cacheFile->getUint32(off);
// Return the first match. We have no rules for conflicting magic data...
// (mime.cache itself is sorted, but what about local overrides with a lower prio?)
candidate = mimeTypeForNameUnchecked(QLatin1String(mimeType));
return;
}
}
}
void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
{
const QByteArray mimeStr = mime.toLatin1();
const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset);
const int numEntries = m_cacheFile->getUint32(parentListOffset);
int begin = 0;
int end = numEntries - 1;
while (begin <= end) {
const int medium = (begin + end) / 2;
const int off = parentListOffset + 4 + 8 * medium;
const int mimeOffset = m_cacheFile->getUint32(off);
const char *aMime = m_cacheFile->getCharStar(mimeOffset);
const int cmp = qstrcmp(aMime, mimeStr);
if (cmp < 0) {
begin = medium + 1;
} else if (cmp > 0) {
end = medium - 1;
} else {
const int parentsOffset = m_cacheFile->getUint32(off + 4);
const int numParents = m_cacheFile->getUint32(parentsOffset);
for (int i = 0; i < numParents; ++i) {
const int parentOffset = m_cacheFile->getUint32(parentsOffset + 4 + 4 * i);
const char *aParent = m_cacheFile->getCharStar(parentOffset);
const QString strParent = QString::fromLatin1(aParent);
if (!result.contains(strParent))
result.append(strParent);
}
break;
}
}
}
QString QMimeBinaryProvider::resolveAlias(const QString &name)
{
const QByteArray input = name.toLatin1();
const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
const int numEntries = m_cacheFile->getUint32(aliasListOffset);
int begin = 0;
int end = numEntries - 1;
while (begin <= end) {
const int medium = (begin + end) / 2;
const int off = aliasListOffset + 4 + 8 * medium;
const int aliasOffset = m_cacheFile->getUint32(off);
const char *alias = m_cacheFile->getCharStar(aliasOffset);
const int cmp = qstrcmp(alias, input);
if (cmp < 0) {
begin = medium + 1;
} else if (cmp > 0) {
end = medium - 1;
} else {
const int mimeOffset = m_cacheFile->getUint32(off + 4);
const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
return QLatin1String(mimeType);
}
}
return QString();
}
void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
{
const QByteArray input = name.toLatin1();
const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
const int numEntries = m_cacheFile->getUint32(aliasListOffset);
for (int pos = 0; pos < numEntries; ++pos) {
const int off = aliasListOffset + 4 + 8 * pos;
const int mimeOffset = m_cacheFile->getUint32(off + 4);
const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
if (input == mimeType) {
const int aliasOffset = m_cacheFile->getUint32(off);
const char *alias = m_cacheFile->getCharStar(aliasOffset);
const QString strAlias = QString::fromLatin1(alias);
if (!result.contains(strAlias))
result.append(strAlias);
}
}
}
void QMimeBinaryProvider::loadMimeTypeList()
{
if (!m_mimetypeListLoaded) {
m_mimetypeListLoaded = true;
m_mimetypeNames.clear();
// Unfortunately mime.cache doesn't have a full list of all mimetypes.
// So we have to parse the plain-text files called "types".
QFile file(m_directory + QStringLiteral("/types"));
if (file.open(QIODevice::ReadOnly)) {
while (!file.atEnd()) {
QByteArray line = file.readLine();
if (line.endsWith('\n'))
line.chop(1);
m_mimetypeNames.insert(QString::fromLatin1(line));
}
}
}
}
void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result)
{
loadMimeTypeList();
if (result.isEmpty()) {
result.reserve(m_mimetypeNames.count());
for (const QString &name : qAsConst(m_mimetypeNames))
result.append(mimeTypeForNameUnchecked(name));
} else {
for (const QString &name : qAsConst(m_mimetypeNames))
if (std::find_if(result.constBegin(), result.constEnd(), [name](const QMimeType &mime) -> bool { return mime.name() == name; })
== result.constEnd())
result.append(mimeTypeForNameUnchecked(name));
}
}
bool QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
{
#ifdef QT_NO_XMLSTREAMREADER
Q_UNUSED(data);
qWarning("Cannot load mime type since QXmlStreamReader is not available.");
return false;
#else
if (data.loaded)
return true;
auto it = m_mimetypeExtra.constFind(data.name);
if (it == m_mimetypeExtra.constEnd()) {
// load comment and globPatterns
// shared-mime-info since 1.3 lowercases the xml files
QString mimeFile = m_directory + QLatin1Char('/') + data.name.toLower() + QLatin1String(".xml");
if (!QFile::exists(mimeFile))
mimeFile = m_directory + QLatin1Char('/') + data.name + QLatin1String(".xml"); // pre-1.3
QFile qfile(mimeFile);
if (!qfile.open(QFile::ReadOnly))
return false;
auto insertIt = m_mimetypeExtra.insert(data.name, MimeTypeExtra{});
it = insertIt;
MimeTypeExtra &extra = insertIt.value();
QString mainPattern;
QXmlStreamReader xml(&qfile);
if (xml.readNextStartElement()) {
if (xml.name() != QLatin1String("mime-type")) {
return false;
}
const auto name = xml.attributes().value(QLatin1String("type"));
if (name.isEmpty())
return false;
if (name.compare(data.name, Qt::CaseInsensitive))
qWarning() << "Got name" << name << "in file" << mimeFile << "expected" << data.name;
while (xml.readNextStartElement()) {
const auto tag = xml.name();
if (tag == QLatin1String("comment")) {
QString lang = xml.attributes().value(QLatin1String("xml:lang")).toString();
const QString text = xml.readElementText();
if (lang.isEmpty()) {
lang = QLatin1String("default"); // no locale attribute provided, treat it as default.
}
extra.localeComments.insert(lang, text);
continue; // we called readElementText, so we're at the EndElement already.
} else if (tag == QLatin1String("glob-deleteall")) { // as written out by shared-mime-info >= 0.70
extra.globPatterns.clear();
mainPattern.clear();
} else if (tag == QLatin1String("glob")) { // as written out by shared-mime-info >= 0.70
const QString pattern = xml.attributes().value(QLatin1String("pattern")).toString();
if (mainPattern.isEmpty() && pattern.startsWith(QLatin1Char('*'))) {
mainPattern = pattern;
}
if (!extra.globPatterns.contains(pattern))
extra.globPatterns.append(pattern);
}
xml.skipCurrentElement();
}
Q_ASSERT(xml.name() == QLatin1String("mime-type"));
}
// Let's assume that shared-mime-info is at least version 0.70
// Otherwise we would need 1) a version check, and 2) code for parsing patterns from the globs file.
if (!mainPattern.isEmpty() &&
(extra.globPatterns.isEmpty() || extra.globPatterns.constFirst() != mainPattern)) {
// ensure it's first in the list of patterns
extra.globPatterns.removeAll(mainPattern);
extra.globPatterns.prepend(mainPattern);
}
}
const MimeTypeExtra &e = it.value();
data.localeComments = e.localeComments;
data.globPatterns = e.globPatterns;
return true;
#endif //QT_NO_XMLSTREAMREADER
}
// Binary search in the icons or generic-icons list
QLatin1String QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime)
{
const int iconsListOffset = cacheFile->getUint32(posListOffset);
const int numIcons = cacheFile->getUint32(iconsListOffset);
int begin = 0;
int end = numIcons - 1;
while (begin <= end) {
const int medium = (begin + end) / 2;
const int off = iconsListOffset + 4 + 8 * medium;
const int mimeOffset = cacheFile->getUint32(off);
const char *mime = cacheFile->getCharStar(mimeOffset);
const int cmp = qstrcmp(mime, inputMime);
if (cmp < 0)
begin = medium + 1;
else if (cmp > 0)
end = medium - 1;
else {
const int iconOffset = cacheFile->getUint32(off + 4);
return QLatin1String(cacheFile->getCharStar(iconOffset));
}
}
return QLatin1String();
}
void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data)
{
const QByteArray inputMime = data.name.toLatin1();
const QLatin1String icon = iconForMime(m_cacheFile, PosIconsListOffset, inputMime);
if (!icon.isEmpty()) {
data.iconName = icon;
}
}
void QMimeBinaryProvider::loadGenericIcon(QMimeTypePrivate &data)
{
const QByteArray inputMime = data.name.toLatin1();
const QLatin1String icon = iconForMime(m_cacheFile, PosGenericIconsListOffset, inputMime);
if (!icon.isEmpty()) {
data.genericIconName = icon;
}
}
////
#if QT_CONFIG(mimetype_database)
static QString internalMimeFileName()
{
return QStringLiteral("<internal MIME data>");
}
QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
: QMimeProviderBase(db, internalMimeFileName())
{
static_assert(sizeof(mimetype_database), "Bundled MIME database is empty");
static_assert(sizeof(mimetype_database) <= MimeTypeDatabaseOriginalSize,
"Compressed MIME database is larger than the original size");
static_assert(MimeTypeDatabaseOriginalSize <= 16*1024*1024,
"Bundled MIME database is too big");
const char *data = reinterpret_cast<const char *>(mimetype_database);
qsizetype size = MimeTypeDatabaseOriginalSize;
#ifdef MIME_DATABASE_IS_ZSTD
// uncompress with libzstd
std::unique_ptr<char []> uncompressed(new char[size]);
size = ZSTD_decompress(uncompressed.get(), size, mimetype_database, sizeof(mimetype_database));
Q_ASSERT(!ZSTD_isError(size));
data = uncompressed.get();
#elif defined(MIME_DATABASE_IS_GZIP)
std::unique_ptr<char []> uncompressed(new char[size]);
z_stream zs = {};
zs.next_in = const_cast<Bytef *>(mimetype_database);
zs.avail_in = sizeof(mimetype_database);
zs.next_out = reinterpret_cast<Bytef *>(uncompressed.get());
zs.avail_out = size;
int res = inflateInit2(&zs, MAX_WBITS | 32);
Q_ASSERT(res == Z_OK);
res = inflate(&zs, Z_FINISH);
Q_ASSERT(res == Z_STREAM_END);
res = inflateEnd(&zs);
Q_ASSERT(res == Z_OK);
data = uncompressed.get();
size = zs.total_out;
#endif
load(data, size);
}
#else // !QT_CONFIG(mimetype_database)
// never called in release mode, but some debug builds may need
// this to be defined.
QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
: QMimeProviderBase(db, QString())
{
Q_UNREACHABLE();
}
#endif // QT_CONFIG(mimetype_database)
QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory)
: QMimeProviderBase(db, directory)
{
ensureLoaded();
}
QMimeXMLProvider::~QMimeXMLProvider()
{
}
bool QMimeXMLProvider::isValid()
{
// If you change this method, adjust the logic in QMimeDatabasePrivate::loadProviders,
// which assumes isValid==false is only possible in QMimeBinaryProvider.
return true;
}
bool QMimeXMLProvider::isInternalDatabase() const
{
#if QT_CONFIG(mimetype_database)
return m_directory == internalMimeFileName();
#else
return false;
#endif
}
QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name)
{
return m_nameMimeTypeMap.value(name);
}
void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
{
m_mimeTypeGlobs.matchingGlobs(fileName, result);
}
void QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate)
{
QString candidateName;
bool foundOne = false;
for (const QMimeMagicRuleMatcher &matcher : qAsConst(m_magicMatchers)) {
if (matcher.matches(data)) {
const int priority = matcher.priority();
if (priority > *accuracyPtr) {
*accuracyPtr = priority;
candidateName = matcher.mimetype();
foundOne = true;
}
}
}
if (foundOne)
candidate = mimeTypeForName(candidateName);
}
void QMimeXMLProvider::ensureLoaded()
{
QStringList allFiles;
const QString packageDir = m_directory + QStringLiteral("/packages");
QDir dir(packageDir);
const QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
allFiles.reserve(files.count());
for (const QString &xmlFile : files)
allFiles.append(packageDir + QLatin1Char('/') + xmlFile);
if (m_allFiles == allFiles)
return;
m_allFiles = allFiles;
m_nameMimeTypeMap.clear();
m_aliases.clear();
m_parents.clear();
m_mimeTypeGlobs.clear();
m_magicMatchers.clear();
//qDebug() << "Loading" << m_allFiles;
for (const QString &file : qAsConst(allFiles))
load(file);
}
void QMimeXMLProvider::load(const QString &fileName)
{
QString errorMessage;
if (!load(fileName, &errorMessage))
qWarning("QMimeDatabase: Error loading %ls\n%ls", qUtf16Printable(fileName), qUtf16Printable(errorMessage));
}
bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (errorMessage)
*errorMessage = QLatin1String("Cannot open ") + fileName + QLatin1String(": ") + file.errorString();
return false;
}
if (errorMessage)
errorMessage->clear();
QMimeTypeParser parser(*this);
return parser.parse(&file, fileName, errorMessage);
}
#if QT_CONFIG(mimetype_database)
void QMimeXMLProvider::load(const char *data, qsizetype len)
{
QBuffer buffer;
buffer.setData(QByteArray::fromRawData(data, len));
buffer.open(QIODevice::ReadOnly);
QString errorMessage;
QMimeTypeParser parser(*this);
if (!parser.parse(&buffer, internalMimeFileName(), &errorMessage))
qWarning("QMimeDatabase: Error loading internal MIME data\n%s", qPrintable(errorMessage));
}
#endif
void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob)
{
m_mimeTypeGlobs.addGlob(glob);
}
void QMimeXMLProvider::addMimeType(const QMimeType &mt)
{
Q_ASSERT(!mt.d.data()->fromCache);
m_nameMimeTypeMap.insert(mt.name(), mt);
}
void QMimeXMLProvider::addParents(const QString &mime, QStringList &result)
{
for (const QString &parent : m_parents.value(mime)) {
if (!result.contains(parent))
result.append(parent);
}
}
void QMimeXMLProvider::addParent(const QString &child, const QString &parent)
{
m_parents[child].append(parent);
}
void QMimeXMLProvider::addAliases(const QString &name, QStringList &result)
{
// Iterate through the whole hash. This method is rarely used.
for (auto it = m_aliases.constBegin(), end = m_aliases.constEnd() ; it != end ; ++it) {
if (it.value() == name) {
if (!result.contains(it.key()))
result.append(it.key());
}
}
}
QString QMimeXMLProvider::resolveAlias(const QString &name)
{
return m_aliases.value(name);
}
void QMimeXMLProvider::addAlias(const QString &alias, const QString &name)
{
m_aliases.insert(alias, name);
}
void QMimeXMLProvider::addAllMimeTypes(QList<QMimeType> &result)
{
if (result.isEmpty()) { // fast path
result = m_nameMimeTypeMap.values();
} else {
for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd() ; it != end ; ++it) {
const QString newMime = it.key();
if (std::find_if(result.constBegin(), result.constEnd(), [newMime](const QMimeType &mime) -> bool { return mime.name() == newMime; })
== result.constEnd())
result.append(it.value());
}
}
}
void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
{
m_magicMatchers.append(matcher);
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,194 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMEPROVIDER_P_H
#define QMIMEPROVIDER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qmimedatabase_p.h"
QT_REQUIRE_CONFIG(mimetype);
#include "qmimeglobpattern_p.h"
#include <QtCore/qdatetime.h>
#include <QtCore/qset.h>
QT_BEGIN_NAMESPACE
class QMimeMagicRuleMatcher;
class QMimeProviderBase
{
public:
QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory);
virtual ~QMimeProviderBase() {}
virtual bool isValid() = 0;
virtual bool isInternalDatabase() const = 0;
virtual QMimeType mimeTypeForName(const QString &name) = 0;
virtual void addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) = 0;
virtual void addParents(const QString &mime, QStringList &result) = 0;
virtual QString resolveAlias(const QString &name) = 0;
virtual void addAliases(const QString &name, QStringList &result) = 0;
virtual void findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) = 0;
virtual void addAllMimeTypes(QList<QMimeType> &result) = 0;
virtual bool loadMimeTypePrivate(QMimeTypePrivate &) { return false; }
virtual void loadIcon(QMimeTypePrivate &) {}
virtual void loadGenericIcon(QMimeTypePrivate &) {}
virtual void ensureLoaded() {}
QString directory() const { return m_directory; }
QMimeDatabasePrivate *m_db;
QString m_directory;
};
/*
Parses the files 'mime.cache' and 'types' on demand
*/
class QMimeBinaryProvider : public QMimeProviderBase
{
public:
QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory);
virtual ~QMimeBinaryProvider();
bool isValid() override;
bool isInternalDatabase() const override;
QMimeType mimeTypeForName(const QString &name) override;
void addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) override;
void addParents(const QString &mime, QStringList &result) override;
QString resolveAlias(const QString &name) override;
void addAliases(const QString &name, QStringList &result) override;
void findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) override;
void addAllMimeTypes(QList<QMimeType> &result) override;
bool loadMimeTypePrivate(QMimeTypePrivate &) override;
void loadIcon(QMimeTypePrivate &) override;
void loadGenericIcon(QMimeTypePrivate &) override;
void ensureLoaded() override;
private:
struct CacheFile;
void matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int offset, const QString &fileName);
bool matchSuffixTree(QMimeGlobMatchResult &result, CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck);
bool matchMagicRule(CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data);
QLatin1String iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime);
void loadMimeTypeList();
bool checkCacheChanged();
CacheFile *m_cacheFile = nullptr;
QStringList m_cacheFileNames;
QSet<QString> m_mimetypeNames;
bool m_mimetypeListLoaded;
struct MimeTypeExtra
{
QHash<QString, QString> localeComments;
QStringList globPatterns;
};
QMap<QString, MimeTypeExtra> m_mimetypeExtra;
};
/*
Parses the raw XML files (slower)
*/
class QMimeXMLProvider : public QMimeProviderBase
{
public:
enum InternalDatabaseEnum { InternalDatabase };
#if QT_CONFIG(mimetype_database)
enum : bool { InternalDatabaseAvailable = true };
#else
enum : bool { InternalDatabaseAvailable = false };
#endif
QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum);
QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory);
~QMimeXMLProvider();
bool isValid() override;
bool isInternalDatabase() const override;
QMimeType mimeTypeForName(const QString &name) override;
void addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) override;
void addParents(const QString &mime, QStringList &result) override;
QString resolveAlias(const QString &name) override;
void addAliases(const QString &name, QStringList &result) override;
void findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) override;
void addAllMimeTypes(QList<QMimeType> &result) override;
void ensureLoaded() override;
bool load(const QString &fileName, QString *errorMessage);
// Called by the mimetype xml parser
void addMimeType(const QMimeType &mt);
void addGlobPattern(const QMimeGlobPattern &glob);
void addParent(const QString &child, const QString &parent);
void addAlias(const QString &alias, const QString &name);
void addMagicMatcher(const QMimeMagicRuleMatcher &matcher);
private:
void load(const QString &fileName);
void load(const char *data, qsizetype len);
typedef QHash<QString, QMimeType> NameMimeTypeMap;
NameMimeTypeMap m_nameMimeTypeMap;
typedef QHash<QString, QString> AliasHash;
AliasHash m_aliases;
typedef QHash<QString, QStringList> ParentsHash;
ParentsHash m_parents;
QMimeAllGlobPatterns m_mimeTypeGlobs;
QList<QMimeMagicRuleMatcher> m_magicMatchers;
QStringList m_allFiles;
};
QT_END_NAMESPACE
#endif // QMIMEPROVIDER_P_H

View File

@@ -0,0 +1,533 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmimetype.h"
#include "qmimetype_p.h"
#include "qmimedatabase_p.h"
#include "qmimeprovider_p.h"
#include "qmimeglobpattern_p.h"
#include <QtCore/QDebug>
#include <QtCore/QLocale>
#include <QtCore/QHashFunctions>
#include <memory>
QT_BEGIN_NAMESPACE
QMimeTypePrivate::QMimeTypePrivate()
: loaded(false), fromCache(false)
{}
QMimeTypePrivate::QMimeTypePrivate(const QMimeType &other)
: loaded(other.d->loaded),
name(other.d->name),
localeComments(other.d->localeComments),
genericIconName(other.d->genericIconName),
iconName(other.d->iconName),
globPatterns(other.d->globPatterns)
{}
void QMimeTypePrivate::clear()
{
name.clear();
localeComments.clear();
genericIconName.clear();
iconName.clear();
globPatterns.clear();
}
void QMimeTypePrivate::addGlobPattern(const QString &pattern)
{
globPatterns.append(pattern);
}
/*!
\class QMimeType
\inmodule QtCore
\ingroup shared
\brief The QMimeType class describes types of file or data, represented by a MIME type string.
\since 5.0
For instance a file named "readme.txt" has the MIME type "text/plain".
The MIME type can be determined from the file name, or from the file
contents, or from both. MIME type determination can also be done on
buffers of data not coming from files.
Determining the MIME type of a file can be useful to make sure your
application supports it. It is also useful in file-manager-like applications
or widgets, in order to display an appropriate \l {QMimeType::iconName}{icon} for the file, or even
the descriptive \l {QMimeType::comment()}{comment} in detailed views.
To check if a file has the expected MIME type, you should use inherits()
rather than a simple string comparison based on the name(). This is because
MIME types can inherit from each other: for instance a C source file is
a specific type of plain text file, so text/x-csrc inherits text/plain.
\sa QMimeDatabase, {MIME Type Browser Example}
*/
/*!
\fn QMimeType &QMimeType::operator=(QMimeType &&other)
Move-assigns \a other to this QMimeType instance.
\since 5.2
*/
/*!
\fn QMimeType::QMimeType();
Constructs this QMimeType object initialized with default property values that indicate an invalid MIME type.
*/
QMimeType::QMimeType() :
d(new QMimeTypePrivate())
{
}
/*!
\fn QMimeType::QMimeType(const QMimeType &other);
Constructs this QMimeType object as a copy of \a other.
*/
QMimeType::QMimeType(const QMimeType &other) :
d(other.d)
{
}
/*!
\fn QMimeType &QMimeType::operator=(const QMimeType &other);
Assigns the data of \a other to this QMimeType object, and returns a reference to this object.
*/
QMimeType &QMimeType::operator=(const QMimeType &other)
{
if (d != other.d)
d = other.d;
return *this;
}
/*!
\fn QMimeType::QMimeType(const QMimeTypePrivate &dd);
Assigns the data of the QMimeTypePrivate \a dd to this QMimeType object, and returns a reference to this object.
\internal
*/
QMimeType::QMimeType(const QMimeTypePrivate &dd) :
d(new QMimeTypePrivate(dd))
{
}
/*!
\fn void QMimeType::swap(QMimeType &other);
Swaps QMimeType \a other with this QMimeType object.
This operation is very fast and never fails.
The swap() method helps with the implementation of assignment
operators in an exception-safe way. For more information consult
\l {http://en.wikibooks.org/wiki/More_C++_Idioms/Copy-and-swap}
{More C++ Idioms - Copy-and-swap}.
*/
/*!
\fn QMimeType::~QMimeType();
Destroys the QMimeType object, and releases the d pointer.
*/
QMimeType::~QMimeType()
{
}
/*!
\fn bool QMimeType::operator==(const QMimeType &other) const;
Returns \c true if \a other equals this QMimeType object, otherwise returns \c false.
The name is the unique identifier for a mimetype, so two mimetypes with
the same name, are equal.
*/
bool QMimeType::operator==(const QMimeType &other) const
{
return d == other.d || d->name == other.d->name;
}
/*!
\since 5.6
\relates QMimeType
Returns the hash value for \a key, using
\a seed to seed the calculation.
*/
size_t qHash(const QMimeType &key, size_t seed) noexcept
{
return qHash(key.d->name, seed);
}
/*!
\fn bool QMimeType::operator!=(const QMimeType &other) const;
Returns \c true if \a other does not equal this QMimeType object, otherwise returns \c false.
*/
/*!
\property QMimeType::valid
\brief \c true if the QMimeType object contains valid data, \c false otherwise
A valid MIME type has a non-empty name().
The invalid MIME type is the default-constructed QMimeType.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
bool QMimeType::isValid() const
{
return !d->name.isEmpty();
}
/*!
\property QMimeType::isDefault
\brief \c true if this MIME type is the default MIME type which
applies to all files: application/octet-stream.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
bool QMimeType::isDefault() const
{
return d->name == QMimeDatabasePrivate::instance()->defaultMimeType();
}
/*!
\property QMimeType::name
\brief the name of the MIME type
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::name() const
{
return d->name;
}
/*!
\property QMimeType::comment
\brief the description of the MIME type to be displayed on user interfaces
The default language (QLocale().name()) is used to select the appropriate translation.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::comment() const
{
QMimeDatabasePrivate::instance()->loadMimeTypePrivate(const_cast<QMimeTypePrivate&>(*d));
QStringList languageList;
languageList << QLocale().name();
languageList << QLocale().uiLanguages();
languageList << QLatin1String("default"); // use the default locale if possible.
for (const QString &language : qAsConst(languageList)) {
const QString lang = language == QLatin1String("C") ? QLatin1String("en_US") : language;
const QString comm = d->localeComments.value(lang);
if (!comm.isEmpty())
return comm;
const int pos = lang.indexOf(QLatin1Char('_'));
if (pos != -1) {
// "pt_BR" not found? try just "pt"
const QString shortLang = lang.left(pos);
const QString commShort = d->localeComments.value(shortLang);
if (!commShort.isEmpty())
return commShort;
}
}
// Use the mimetype name as fallback
return d->name;
}
/*!
\property QMimeType::genericIconName
\brief the file name of a generic icon that represents the MIME type
This should be used if the icon returned by iconName() cannot be found on
the system. It is used for categories of similar types (like spreadsheets
or archives) that can use a common icon.
The freedesktop.org Icon Naming Specification lists a set of such icon names.
The icon name can be given to QIcon::fromTheme() in order to load the icon.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::genericIconName() const
{
QMimeDatabasePrivate::instance()->loadGenericIcon(const_cast<QMimeTypePrivate&>(*d));
if (d->genericIconName.isEmpty()) {
// From the spec:
// If the generic icon name is empty (not specified by the mimetype definition)
// then the mimetype is used to generate the generic icon by using the top-level
// media type (e.g. "video" in "video/ogg") and appending "-x-generic"
// (i.e. "video-x-generic" in the previous example).
const QString group = name();
QStringView groupRef(group);
const int slashindex = groupRef.indexOf(QLatin1Char('/'));
if (slashindex != -1)
groupRef = groupRef.left(slashindex);
return groupRef + QLatin1String("-x-generic");
}
return d->genericIconName;
}
static QString make_default_icon_name_from_mimetype_name(QString iconName)
{
const int slashindex = iconName.indexOf(QLatin1Char('/'));
if (slashindex != -1)
iconName[slashindex] = QLatin1Char('-');
return iconName;
}
/*!
\property QMimeType::iconName
\brief the file name of an icon image that represents the MIME type
The icon name can be given to QIcon::fromTheme() in order to load the icon.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::iconName() const
{
QMimeDatabasePrivate::instance()->loadIcon(const_cast<QMimeTypePrivate&>(*d));
if (d->iconName.isEmpty()) {
return make_default_icon_name_from_mimetype_name(name());
}
return d->iconName;
}
/*!
\property QMimeType::globPatterns
\brief the list of glob matching patterns
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QStringList QMimeType::globPatterns() const
{
QMimeDatabasePrivate::instance()->loadMimeTypePrivate(const_cast<QMimeTypePrivate&>(*d));
return d->globPatterns;
}
/*!
\property QMimeType::parentMimeTypes
\brief the names of parent MIME types
A type is a subclass of another type if any instance of the first type is
also an instance of the second. For example, all image/svg+xml files are also
text/xml, text/plain and application/octet-stream files. Subclassing is about
the format, rather than the category of the data (for example, there is no
'generic spreadsheet' class that all spreadsheets inherit from).
Conversely, the parent mimetype of image/svg+xml is text/xml.
A mimetype can have multiple parents. For instance application/x-perl
has two parents: application/x-executable and text/plain. This makes
it possible to both execute perl scripts, and to open them in text editors.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QStringList QMimeType::parentMimeTypes() const
{
return QMimeDatabasePrivate::instance()->mimeParents(d->name);
}
static void collectParentMimeTypes(const QString &mime, QStringList &allParents)
{
const QStringList parents = QMimeDatabasePrivate::instance()->mimeParents(mime);
for (const QString &parent : parents) {
// I would use QSet, but since order matters I better not
if (!allParents.contains(parent))
allParents.append(parent);
}
// We want a breadth-first search, so that the least-specific parent (octet-stream) is last
// This means iterating twice, unfortunately.
for (const QString &parent : parents)
collectParentMimeTypes(parent, allParents);
}
/*!
\property QMimeType::allAncestors
\brief the names of direct and indirect parent MIME types
Return all the parent mimetypes of this mimetype, direct and indirect.
This includes the parent(s) of its parent(s), etc.
For instance, for image/svg+xml the list would be:
application/xml, text/plain, application/octet-stream.
Note that application/octet-stream is the ultimate parent for all types
of files (but not directories).
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QStringList QMimeType::allAncestors() const
{
QStringList allParents;
collectParentMimeTypes(d->name, allParents);
return allParents;
}
/*!
\property QMimeType::aliases
\brief the list of aliases of this mimetype
For instance, for text/csv, the returned list would be:
text/x-csv, text/x-comma-separated-values.
Note that all QMimeType instances refer to proper mimetypes,
never to aliases directly.
The order of the aliases in the list is undefined.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QStringList QMimeType::aliases() const
{
return QMimeDatabasePrivate::instance()->listAliases(d->name);
}
/*!
\property QMimeType::suffixes
\brief the known suffixes for the MIME type
No leading dot is included, so for instance this would return "jpg", "jpeg" for image/jpeg.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QStringList QMimeType::suffixes() const
{
QMimeDatabasePrivate::instance()->loadMimeTypePrivate(const_cast<QMimeTypePrivate&>(*d));
QStringList result;
for (const QString &pattern : qAsConst(d->globPatterns)) {
// Not a simple suffix if it looks like: README or *. or *.* or *.JP*G or *.JP?
if (pattern.startsWith(QLatin1String("*.")) &&
pattern.length() > 2 &&
pattern.indexOf(QLatin1Char('*'), 2) < 0 && pattern.indexOf(QLatin1Char('?'), 2) < 0) {
const QString suffix = pattern.mid(2);
result.append(suffix);
}
}
return result;
}
/*!
\property QMimeType::preferredSuffix
\brief the preferred suffix for the MIME type
No leading dot is included, so for instance this would return "pdf" for application/pdf.
The return value can be empty, for mime types which do not have any suffixes associated.
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::preferredSuffix() const
{
if (isDefault()) // workaround for unwanted *.bin suffix for octet-stream, https://bugs.freedesktop.org/show_bug.cgi?id=101667, fixed upstream in 1.10
return QString();
const QStringList suffixList = suffixes();
return suffixList.isEmpty() ? QString() : suffixList.at(0);
}
/*!
\property QMimeType::filterString
\brief a filter string usable for a file dialog
While this property was introduced in 5.10, the
corresponding accessor method has always been there.
*/
QString QMimeType::filterString() const
{
QMimeDatabasePrivate::instance()->loadMimeTypePrivate(const_cast<QMimeTypePrivate&>(*d));
QString filter;
if (!d->globPatterns.empty()) {
filter += comment() + QLatin1String(" (");
for (int i = 0; i < d->globPatterns.size(); ++i) {
if (i != 0)
filter += QLatin1Char(' ');
filter += d->globPatterns.at(i);
}
filter += QLatin1Char(')');
}
return filter;
}
/*!
\fn bool QMimeType::inherits(const QString &mimeTypeName) const;
Returns \c true if this mimetype is \a mimeTypeName,
or inherits \a mimeTypeName (see parentMimeTypes()),
or \a mimeTypeName is an alias for this mimetype.
This method has been made invokable from QML since 5.10.
*/
bool QMimeType::inherits(const QString &mimeTypeName) const
{
if (d->name == mimeTypeName)
return true;
return QMimeDatabasePrivate::instance()->mimeInherits(d->name, mimeTypeName);
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QMimeType &mime)
{
QDebugStateSaver saver(debug);
if (!mime.isValid()) {
debug.nospace() << "QMimeType(invalid)";
} else {
debug.nospace() << "QMimeType(" << mime.name() << ")";
}
return debug;
}
#endif
QT_END_NAMESPACE
#include "moc_qmimetype.cpp"

View File

@@ -0,0 +1,136 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMETYPE_H
#define QMIMETYPE_H
#include <QtCore/qglobal.h>
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qobjectdefs.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
QT_BEGIN_NAMESPACE
class QMimeTypePrivate;
class QMimeType;
Q_CORE_EXPORT size_t qHash(const QMimeType &key, size_t seed = 0) noexcept;
class Q_CORE_EXPORT QMimeType
{
Q_GADGET
Q_PROPERTY(bool valid READ isValid CONSTANT)
Q_PROPERTY(bool isDefault READ isDefault CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString comment READ comment CONSTANT)
Q_PROPERTY(QString genericIconName READ genericIconName CONSTANT)
Q_PROPERTY(QString iconName READ iconName CONSTANT)
Q_PROPERTY(QStringList globPatterns READ globPatterns CONSTANT)
Q_PROPERTY(QStringList parentMimeTypes READ parentMimeTypes CONSTANT)
Q_PROPERTY(QStringList allAncestors READ allAncestors CONSTANT)
Q_PROPERTY(QStringList aliases READ aliases CONSTANT)
Q_PROPERTY(QStringList suffixes READ suffixes CONSTANT)
Q_PROPERTY(QString preferredSuffix READ preferredSuffix CONSTANT)
Q_PROPERTY(QString filterString READ filterString CONSTANT)
public:
QMimeType();
QMimeType(const QMimeType &other);
QMimeType &operator=(const QMimeType &other);
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QMimeType)
void swap(QMimeType &other) noexcept
{
d.swap(other.d);
}
explicit QMimeType(const QMimeTypePrivate &dd);
~QMimeType();
bool operator==(const QMimeType &other) const;
inline bool operator!=(const QMimeType &other) const
{
return !operator==(other);
}
bool isValid() const;
bool isDefault() const;
QString name() const;
QString comment() const;
QString genericIconName() const;
QString iconName() const;
QStringList globPatterns() const;
QStringList parentMimeTypes() const;
QStringList allAncestors() const;
QStringList aliases() const;
QStringList suffixes() const;
QString preferredSuffix() const;
Q_INVOKABLE bool inherits(const QString &mimeTypeName) const;
QString filterString() const;
protected:
friend class QMimeTypeParserBase;
friend class MimeTypeMapEntry;
friend class QMimeDatabasePrivate;
friend class QMimeXMLProvider;
friend class QMimeBinaryProvider;
friend class QMimeTypePrivate;
friend Q_CORE_EXPORT size_t qHash(const QMimeType &key, size_t seed) noexcept;
QExplicitlySharedDataPointer<QMimeTypePrivate> d;
};
Q_DECLARE_SHARED(QMimeType)
#ifndef QT_NO_DEBUG_STREAM
class QDebug;
Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QMimeType &mime);
#endif
QT_END_NAMESPACE
#endif // QMIMETYPE_H

View File

@@ -0,0 +1,106 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMETYPE_P_H
#define QMIMETYPE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/private/qglobal_p.h>
#include "qmimetype.h"
QT_REQUIRE_CONFIG(mimetype);
#include <QtCore/qhash.h>
#include <QtCore/qstringlist.h>
QT_BEGIN_NAMESPACE
class Q_AUTOTEST_EXPORT QMimeTypePrivate : public QSharedData
{
public:
typedef QHash<QString, QString> LocaleHash;
QMimeTypePrivate();
explicit QMimeTypePrivate(const QMimeType &other);
void clear();
void addGlobPattern(const QString &pattern);
bool loaded; // QSharedData leaves a 4 byte gap, so don't put 8 byte members first
bool fromCache; // true if this comes from the binary provider
QString name;
LocaleHash localeComments;
QString genericIconName;
QString iconName;
QStringList globPatterns;
};
QT_END_NAMESPACE
#define QMIMETYPE_BUILDER_FROM_RVALUE_REFS \
QT_BEGIN_NAMESPACE \
static QMimeType buildQMimeType ( \
QString &&name, \
QString &&genericIconName, \
QString &&iconName, \
QStringList &&globPatterns \
) \
{ \
QMimeTypePrivate qMimeTypeData; \
qMimeTypeData.loaded = true; \
qMimeTypeData.name = std::move(name); \
qMimeTypeData.genericIconName = std::move(genericIconName); \
qMimeTypeData.iconName = std::move(iconName); \
qMimeTypeData.globPatterns = std::move(globPatterns); \
return QMimeType(qMimeTypeData); \
} \
QT_END_NAMESPACE
#endif // QMIMETYPE_P_H

View File

@@ -0,0 +1,350 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#define QT_NO_CAST_FROM_ASCII
#include "qmimetypeparser_p.h"
#include "qmimetype_p.h"
#include "qmimemagicrulematcher_p.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamWriter>
#include <QtCore/QStack>
QT_BEGIN_NAMESPACE
// XML tags in MIME files
static const char mimeInfoTagC[] = "mime-info";
static const char mimeTypeTagC[] = "mime-type";
static const char mimeTypeAttributeC[] = "type";
static const char subClassTagC[] = "sub-class-of";
static const char commentTagC[] = "comment";
static const char genericIconTagC[] = "generic-icon";
static const char iconTagC[] = "icon";
static const char nameAttributeC[] = "name";
static const char globTagC[] = "glob";
static const char globDeleteAllTagC[] = "glob-deleteall";
static const char aliasTagC[] = "alias";
static const char patternAttributeC[] = "pattern";
static const char weightAttributeC[] = "weight";
static const char caseSensitiveAttributeC[] = "case-sensitive";
static const char localeAttributeC[] = "xml:lang";
static const char magicTagC[] = "magic";
static const char priorityAttributeC[] = "priority";
static const char matchTagC[] = "match";
static const char matchValueAttributeC[] = "value";
static const char matchTypeAttributeC[] = "type";
static const char matchOffsetAttributeC[] = "offset";
static const char matchMaskAttributeC[] = "mask";
/*!
\class QMimeTypeParser
\inmodule QtCore
\internal
\brief The QMimeTypeParser class parses MIME types, and builds a MIME database hierarchy by adding to QMimeDatabase.
Populates QMimeDataBase
\sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
\sa QMimeTypeParser
*/
/*!
\class QMimeTypeParserBase
\inmodule QtCore
\internal
\brief The QMimeTypeParserBase class parses for a sequence of <mime-type> in a generic way.
Calls abstract handler function process for QMimeType it finds.
\sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
\sa QMimeTypeParser
*/
/*!
\fn virtual bool QMimeTypeParserBase::process(const QMimeType &t, QString *errorMessage) = 0;
Overwrite to process the sequence of parsed data
*/
QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState currentState, QStringView startElement)
{
switch (currentState) {
case ParseBeginning:
if (startElement == QLatin1String(mimeInfoTagC))
return ParseMimeInfo;
if (startElement == QLatin1String(mimeTypeTagC))
return ParseMimeType;
return ParseError;
case ParseMimeInfo:
return startElement == QLatin1String(mimeTypeTagC) ? ParseMimeType : ParseError;
case ParseMimeType:
case ParseComment:
case ParseGenericIcon:
case ParseIcon:
case ParseGlobPattern:
case ParseGlobDeleteAll:
case ParseSubClass:
case ParseAlias:
case ParseOtherMimeTypeSubTag:
case ParseMagicMatchRule:
if (startElement == QLatin1String(mimeTypeTagC)) // Sequence of <mime-type>
return ParseMimeType;
if (startElement == QLatin1String(commentTagC))
return ParseComment;
if (startElement == QLatin1String(genericIconTagC))
return ParseGenericIcon;
if (startElement == QLatin1String(iconTagC))
return ParseIcon;
if (startElement == QLatin1String(globTagC))
return ParseGlobPattern;
if (startElement == QLatin1String(globDeleteAllTagC))
return ParseGlobDeleteAll;
if (startElement == QLatin1String(subClassTagC))
return ParseSubClass;
if (startElement == QLatin1String(aliasTagC))
return ParseAlias;
if (startElement == QLatin1String(magicTagC))
return ParseMagic;
if (startElement == QLatin1String(matchTagC))
return ParseMagicMatchRule;
return ParseOtherMimeTypeSubTag;
case ParseMagic:
if (startElement == QLatin1String(matchTagC))
return ParseMagicMatchRule;
break;
case ParseError:
break;
}
return ParseError;
}
// Parse int number from an (attribute) string
bool QMimeTypeParserBase::parseNumber(QStringView n, int *target, QString *errorMessage)
{
bool ok;
*target = n.toInt(&ok);
if (Q_UNLIKELY(!ok)) {
if (errorMessage)
*errorMessage = QLatin1String("Not a number '") + n + QLatin1String("'.");
return false;
}
return true;
}
#ifndef QT_NO_XMLSTREAMREADER
struct CreateMagicMatchRuleResult
{
QString errorMessage; // must be first
QMimeMagicRule rule;
CreateMagicMatchRuleResult(QStringView type, QStringView value, QStringView offsets, QStringView mask)
: errorMessage(), rule(type.toString(), value.toUtf8(), offsets.toString(), mask.toLatin1(), &errorMessage)
{
}
};
static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttributes &atts)
{
const auto type = atts.value(QLatin1String(matchTypeAttributeC));
const auto value = atts.value(QLatin1String(matchValueAttributeC));
const auto offsets = atts.value(QLatin1String(matchOffsetAttributeC));
const auto mask = atts.value(QLatin1String(matchMaskAttributeC));
return CreateMagicMatchRuleResult(type, value, offsets, mask);
}
#endif
bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
{
#ifdef QT_NO_XMLSTREAMREADER
Q_UNUSED(dev);
if (errorMessage)
*errorMessage = QString::fromLatin1("QXmlStreamReader is not available, cannot parse '%1'.").arg(fileName);
return false;
#else
QMimeTypePrivate data;
data.loaded = true;
int priority = 50;
QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
QList<QMimeMagicRule> rules; // toplevel rules
QXmlStreamReader reader(dev);
ParseState ps = ParseBeginning;
while (!reader.atEnd()) {
switch (reader.readNext()) {
case QXmlStreamReader::StartElement: {
ps = nextState(ps, reader.name());
const QXmlStreamAttributes atts = reader.attributes();
switch (ps) {
case ParseMimeType: { // start parsing a MIME type name
const QString name = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
if (name.isEmpty()) {
reader.raiseError(QStringLiteral("Missing 'type'-attribute"));
} else {
data.name = name;
}
}
break;
case ParseGenericIcon:
data.genericIconName = atts.value(QLatin1String(nameAttributeC)).toString();
break;
case ParseIcon:
data.iconName = atts.value(QLatin1String(nameAttributeC)).toString();
break;
case ParseGlobPattern: {
const QString pattern = atts.value(QLatin1String(patternAttributeC)).toString();
unsigned weight = atts.value(QLatin1String(weightAttributeC)).toInt();
const bool caseSensitive = atts.value(QLatin1String(caseSensitiveAttributeC)) == QLatin1String("true");
if (weight == 0)
weight = QMimeGlobPattern::DefaultWeight;
Q_ASSERT(!data.name.isEmpty());
const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
if (!process(glob, errorMessage)) // for actual glob matching
return false;
data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
}
break;
case ParseGlobDeleteAll:
data.globPatterns.clear();
break;
case ParseSubClass: {
const QString inheritsFrom = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
if (!inheritsFrom.isEmpty())
processParent(data.name, inheritsFrom);
}
break;
case ParseComment: {
// comments have locale attributes.
QString locale = atts.value(QLatin1String(localeAttributeC)).toString();
const QString comment = reader.readElementText();
if (locale.isEmpty())
locale = QString::fromLatin1("default");
data.localeComments.insert(locale, comment);
}
break;
case ParseAlias: {
const QString alias = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
if (!alias.isEmpty())
processAlias(alias, data.name);
}
break;
case ParseMagic: {
priority = 50;
const auto priorityS = atts.value(QLatin1String(priorityAttributeC));
if (!priorityS.isEmpty()) {
if (!parseNumber(priorityS, &priority, errorMessage))
return false;
}
currentRules.clear();
//qDebug() << "MAGIC start for mimetype" << data.name;
}
break;
case ParseMagicMatchRule: {
auto result = createMagicMatchRule(atts);
if (Q_UNLIKELY(!result.rule.isValid()))
qWarning("QMimeDatabase: Error parsing %ls\n%ls",
qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
QList<QMimeMagicRule> *ruleList;
if (currentRules.isEmpty())
ruleList = &rules;
else // nest this rule into the proper parent
ruleList = &currentRules.top()->m_subMatches;
ruleList->append(std::move(result.rule));
//qDebug() << " MATCH added. Stack size was" << currentRules.size();
currentRules.push(&ruleList->last());
break;
}
case ParseError:
reader.raiseError(QLatin1String("Unexpected element <") + reader.name() + QLatin1Char('>'));
break;
default:
break;
}
}
break;
// continue switch QXmlStreamReader::Token...
case QXmlStreamReader::EndElement: // Finished element
{
const auto elementName = reader.name();
if (elementName == QLatin1String(mimeTypeTagC)) {
if (!process(QMimeType(data), errorMessage))
return false;
data.clear();
} else if (elementName == QLatin1String(matchTagC)) {
// Closing a <match> tag, pop stack
currentRules.pop();
//qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
} else if (elementName == QLatin1String(magicTagC)) {
//qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
// Finished a <magic> sequence
QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
ruleMatcher.addRules(rules);
processMagicMatcher(ruleMatcher);
rules.clear();
}
break;
}
default:
break;
}
}
if (Q_UNLIKELY(reader.hasError())) {
if (errorMessage) {
*errorMessage = QString::asprintf("An error has been encountered at line %lld of %ls: %ls:",
reader.lineNumber(),
qUtf16Printable(fileName),
qUtf16Printable(reader.errorString()));
}
return false;
}
return true;
#endif //QT_NO_XMLSTREAMREADER
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,133 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMIMETYPEPARSER_P_H
#define QMIMETYPEPARSER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qmimedatabase_p.h"
QT_REQUIRE_CONFIG(mimetype);
#include "qmimeprovider_p.h"
QT_BEGIN_NAMESPACE
class QIODevice;
class QMimeTypeParserBase
{
Q_DISABLE_COPY_MOVE(QMimeTypeParserBase)
public:
QMimeTypeParserBase() {}
virtual ~QMimeTypeParserBase() {}
bool parse(QIODevice *dev, const QString &fileName, QString *errorMessage);
static bool parseNumber(QStringView n, int *target, QString *errorMessage);
protected:
virtual bool process(const QMimeType &t, QString *errorMessage) = 0;
virtual bool process(const QMimeGlobPattern &t, QString *errorMessage) = 0;
virtual void processParent(const QString &child, const QString &parent) = 0;
virtual void processAlias(const QString &alias, const QString &name) = 0;
virtual void processMagicMatcher(const QMimeMagicRuleMatcher &matcher) = 0;
private:
enum ParseState {
ParseBeginning,
ParseMimeInfo,
ParseMimeType,
ParseComment,
ParseGenericIcon,
ParseIcon,
ParseGlobPattern,
ParseGlobDeleteAll,
ParseSubClass,
ParseAlias,
ParseMagic,
ParseMagicMatchRule,
ParseOtherMimeTypeSubTag,
ParseError
};
static ParseState nextState(ParseState currentState, QStringView startElement);
};
class QMimeTypeParser : public QMimeTypeParserBase
{
public:
explicit QMimeTypeParser(QMimeXMLProvider &provider) : m_provider(provider) {}
protected:
inline bool process(const QMimeType &t, QString *) override
{ m_provider.addMimeType(t); return true; }
inline bool process(const QMimeGlobPattern &glob, QString *) override
{ m_provider.addGlobPattern(glob); return true; }
inline void processParent(const QString &child, const QString &parent) override
{ m_provider.addParent(child, parent); }
inline void processAlias(const QString &alias, const QString &name) override
{ m_provider.addAlias(alias, name); }
inline void processMagicMatcher(const QMimeMagicRuleMatcher &matcher) override
{ m_provider.addMagicMatcher(matcher); }
private:
QMimeXMLProvider &m_provider;
};
QT_END_NAMESPACE
#endif // MIMETYPEPARSER_P_H