diff --git a/src/libs/utils/mimetypes2/qmimedatabase.cpp b/src/libs/utils/mimetypes2/qmimedatabase.cpp new file mode 100644 index 00000000000..f7cd9262208 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimedatabase.cpp @@ -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 +** 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 // always first + +#include "qmimedatabase.h" +#include "qmimedatabase_p.h" + +#include "qmimeprovider_p.h" +#include "qmimetype_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 &prov) + { + return prov && prov->directory() == mimeDir; + }; + const auto it = std::find_if(currentProviders.begin(), currentProviders.end(), predicate); + if (it == currentProviders.end()) { + std::unique_ptr 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 &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(*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 QMimeDatabasePrivate::allMimeTypes() +{ + QList 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 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 QMimeDatabase::mimeTypesForFileName(const QString &fileName) const +{ + QMutexLocker locker(&d->mutex); + + const QStringList matches = d->mimeTypeForFileName(fileName); + QList 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(&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 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 diff --git a/src/libs/utils/mimetypes2/qmimedatabase.h b/src/libs/utils/mimetypes2/qmimedatabase.h new file mode 100644 index 00000000000..9111e5a04c9 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimedatabase.h @@ -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 +** 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 + +QT_REQUIRE_CONFIG(mimetype); + +#include + +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 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 allMimeTypes() const; + +private: + QMimeDatabasePrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QMIMEDATABASE_H diff --git a/src/libs/utils/mimetypes2/qmimedatabase_p.h b/src/libs/utils/mimetypes2/qmimedatabase_p.h new file mode 100644 index 00000000000..e6af23e7edc --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimedatabase_p.h @@ -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 +** 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 +#include +#include + +#include +#include + +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 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>; + 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 diff --git a/src/libs/utils/mimetypes2/qmimeglobpattern.cpp b/src/libs/utils/mimetypes2/qmimeglobpattern.cpp new file mode 100644 index 00000000000..d36c29a82d0 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimeglobpattern.cpp @@ -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 +#endif +#include +#include + +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 diff --git a/src/libs/utils/mimetypes2/qmimeglobpattern_p.h b/src/libs/utils/mimetypes2/qmimeglobpattern_p.h new file mode 100644 index 00000000000..584e3329a6e --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimeglobpattern_p.h @@ -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 + +QT_REQUIRE_CONFIG(mimetype); + +#include +#include + +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 +{ +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 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 diff --git a/src/libs/utils/mimetypes2/qmimemagicrule.cpp b/src/libs/utils/mimetypes2/qmimemagicrule.cpp new file mode 100644 index 00000000000..2c8c2e71991 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimemagicrule.cpp @@ -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 +#include +#include + +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 +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(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 +// +// + +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; + } + break; + case Big16: + case Little16: + if (m_number <= quint16(-1)) { + m_number = m_type == Little16 ? qFromLittleEndian(m_number) : qFromBigEndian(m_number); + if (m_numberMask != 0) + m_numberMask = m_type == Little16 ? qFromLittleEndian(m_numberMask) : qFromBigEndian(m_numberMask); + } + Q_FALLTHROUGH(); + case Host16: + if (m_number <= quint16(-1)) { + if (m_numberMask == 0) + m_numberMask = quint16(-1); + m_matchFunction = &QMimeMagicRule::matchNumber; + } + break; + case Big32: + case Little32: + m_number = m_type == Little32 ? qFromLittleEndian(m_number) : qFromBigEndian(m_number); + if (m_numberMask != 0) + m_numberMask = m_type == Little32 ? qFromLittleEndian(m_numberMask) : qFromBigEndian(m_numberMask); + Q_FALLTHROUGH(); + case Host32: + if (m_numberMask == 0) + m_numberMask = quint32(-1); + m_matchFunction = &QMimeMagicRule::matchNumber; + 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::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 diff --git a/src/libs/utils/mimetypes2/qmimemagicrule_p.h b/src/libs/utils/mimetypes2/qmimemagicrule_p.h new file mode 100644 index 00000000000..5091d483b64 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimemagicrule_p.h @@ -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 + +QT_REQUIRE_CONFIG(mimetype); + +#include +#include +#include + +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 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 + bool matchNumber(const QByteArray &data) const; +}; +Q_DECLARE_SHARED(QMimeMagicRule) + +QT_END_NAMESPACE + +#endif // QMIMEMAGICRULE_H diff --git a/src/libs/utils/mimetypes2/qmimemagicrulematcher.cpp b/src/libs/utils/mimetypes2/qmimemagicrulematcher.cpp new file mode 100644 index 00000000000..8f44fd2f801 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimemagicrulematcher.cpp @@ -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 &rules) +{ + m_list.append(rules); +} + +QList 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 diff --git a/src/libs/utils/mimetypes2/qmimemagicrulematcher_p.h b/src/libs/utils/mimetypes2/qmimemagicrulematcher_p.h new file mode 100644 index 00000000000..fe714987e6b --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimemagicrulematcher_p.h @@ -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 +#include +#include + +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 &rules); + QList magicRules() const; + + bool matches(const QByteArray &data) const; + + unsigned priority() const; + + QString mimetype() const { return m_mimetype; } + +private: + QList m_list; + unsigned m_priority; + QString m_mimetype; +}; +Q_DECLARE_SHARED(QMimeMagicRuleMatcher) + +QT_END_NAMESPACE + +#endif // QMIMEMAGICRULEMATCHER_P_H diff --git a/src/libs/utils/mimetypes2/qmimeprovider.cpp b/src/libs/utils/mimetypes2/qmimeprovider.cpp new file mode 100644 index 00000000000..f152094dcd8 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimeprovider.cpp @@ -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 +** 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 +#include "qmimemagicrulematcher_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +# 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 +# include +# 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(data + offset)); + } + inline quint32 getUint32(int offset) const + { + return qFromBigEndian(*reinterpret_cast(data + offset)); + } + inline const char *getCharStar(int offset) const + { + return reinterpret_cast(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 &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(""); +} + +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(mimetype_database); + qsizetype size = MimeTypeDatabaseOriginalSize; + +#ifdef MIME_DATABASE_IS_ZSTD + // uncompress with libzstd + std::unique_ptr 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 uncompressed(new char[size]); + z_stream zs = {}; + zs.next_in = const_cast(mimetype_database); + zs.avail_in = sizeof(mimetype_database); + zs.next_out = reinterpret_cast(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 &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 diff --git a/src/libs/utils/mimetypes2/qmimeprovider_p.h b/src/libs/utils/mimetypes2/qmimeprovider_p.h new file mode 100644 index 00000000000..affc5546980 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimeprovider_p.h @@ -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 +** 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 +#include + +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 &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 &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 m_mimetypeNames; + bool m_mimetypeListLoaded; + struct MimeTypeExtra + { + QHash localeComments; + QStringList globPatterns; + }; + QMap 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 &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 NameMimeTypeMap; + NameMimeTypeMap m_nameMimeTypeMap; + + typedef QHash AliasHash; + AliasHash m_aliases; + + typedef QHash ParentsHash; + ParentsHash m_parents; + QMimeAllGlobPatterns m_mimeTypeGlobs; + + QList m_magicMatchers; + QStringList m_allFiles; +}; + +QT_END_NAMESPACE + +#endif // QMIMEPROVIDER_P_H diff --git a/src/libs/utils/mimetypes2/qmimetype.cpp b/src/libs/utils/mimetypes2/qmimetype.cpp new file mode 100644 index 00000000000..43086657308 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimetype.cpp @@ -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 +** 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 +#include +#include + +#include + +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(*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(*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(*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(*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(*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(*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" diff --git a/src/libs/utils/mimetypes2/qmimetype.h b/src/libs/utils/mimetypes2/qmimetype.h new file mode 100644 index 00000000000..7424bcdf7a2 --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimetype.h @@ -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 +** 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 + +QT_REQUIRE_CONFIG(mimetype); + +#include +#include +#include +#include + +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 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 diff --git a/src/libs/utils/mimetypes2/qmimetype_p.h b/src/libs/utils/mimetypes2/qmimetype_p.h new file mode 100644 index 00000000000..0d6b4b4b12e --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimetype_p.h @@ -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 +#include "qmimetype.h" + +QT_REQUIRE_CONFIG(mimetype); + +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QMimeTypePrivate : public QSharedData +{ +public: + typedef QHash 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 diff --git a/src/libs/utils/mimetypes2/qmimetypeparser.cpp b/src/libs/utils/mimetypes2/qmimetypeparser.cpp new file mode 100644 index 00000000000..ff9936d335a --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimetypeparser.cpp @@ -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 +#include +#include +#include +#include +#include + +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 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 + 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 currentRules; // stack for the nesting of rules + QList 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 *ruleList; + if (currentRules.isEmpty()) + ruleList = &rules; + else // nest this rule into the proper parent + ruleList = ¤tRules.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 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 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 diff --git a/src/libs/utils/mimetypes2/qmimetypeparser_p.h b/src/libs/utils/mimetypes2/qmimetypeparser_p.h new file mode 100644 index 00000000000..9634718ef1c --- /dev/null +++ b/src/libs/utils/mimetypes2/qmimetypeparser_p.h @@ -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