1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <qmimedatabase.h>
30
31#include "qstandardpaths.h"
32
33#ifdef Q_OS_UNIX
34#include <sys/types.h>
35#include <sys/stat.h>
36#endif
37
38#include <QtCore/QElapsedTimer>
39#include <QtCore/QFile>
40#include <QtCore/QFileInfo>
41#include <QtCore/QStandardPaths>
42#include <QtCore/QTemporaryDir>
43#include <QtCore/QTextStream>
44#include <QtConcurrent/QtConcurrentRun>
45
46#include <QtTest/QtTest>
47
48static const char *const additionalMimeFiles[] = {
49 "yast2-metapackage-handler-mimetypes.xml",
50 "qml-again.xml",
51 "text-x-objcsrc.xml",
52 "invalid-magic1.xml",
53 "invalid-magic2.xml",
54 "invalid-magic3.xml",
55 "magic-and-hierarchy.xml",
56 0
57};
58
59#define RESOURCE_PREFIX ":/qt-project.org/qmime/"
60
61void initializeLang()
62{
63 qputenv(varName: "LC_ALL", value: "");
64 qputenv(varName: "LANG", value: "C");
65 QCoreApplication::setApplicationName("tst_qmimedatabase"); // temporary directory pattern
66}
67
68static inline QString testSuiteWarning()
69{
70
71 QString result;
72 QTextStream str(&result);
73 str << "\nCannot find the shared-mime-info test suite\nstarting from: "
74 << QDir::toNativeSeparators(pathName: QDir::currentPath()) << "\n"
75 "cd " << QDir::toNativeSeparators(QStringLiteral("tests/auto/corelib/mimetypes/qmimedatabase")) << "\n"
76 "wget http://cgit.freedesktop.org/xdg/shared-mime-info/snapshot/Release-1-10.zip\n"
77 "unzip Release-1-10.zip\n";
78#ifdef Q_OS_WIN
79 str << "mkdir testfiles\nxcopy /s Release-1-10 s-m-i\n";
80#else
81 str << "ln -s Release-1-10 s-m-i\n";
82#endif
83 return result;
84}
85
86static bool copyResourceFile(const QString &sourceFileName, const QString &targetFileName,
87 QString *errorMessage)
88{
89
90 QFile sourceFile(sourceFileName);
91 if (!sourceFile.exists()) {
92 *errorMessage = QDir::toNativeSeparators(pathName: sourceFileName) + QLatin1String(" does not exist.");
93 return false;
94 }
95 if (!sourceFile.copy(newName: targetFileName)) {
96 *errorMessage = QLatin1String("Cannot copy ")
97 + QDir::toNativeSeparators(pathName: sourceFileName) + QLatin1String(" to ")
98 + QDir::toNativeSeparators(pathName: targetFileName) + QLatin1String(": ")
99 + sourceFile.errorString();
100 return false;
101 }
102 // QFile::copy() sets the permissions of the source file which are read-only for
103 // resource files. Set write permission to enable deletion of the temporary directory.
104 QFile targetFile(targetFileName);
105 if (!targetFile.setPermissions(targetFile.permissions() | QFileDevice::WriteUser)) {
106 *errorMessage = QLatin1String("Cannot set write permission on ")
107 + QDir::toNativeSeparators(pathName: targetFileName) + QLatin1String(": ")
108 + targetFile.errorString();
109 return false;
110 }
111 return true;
112}
113
114// Set LANG before QCoreApplication is created
115Q_CONSTRUCTOR_FUNCTION(initializeLang)
116
117static QString seedAndTemplate()
118{
119 return QDir::tempPath() + "/tst_qmimedatabase-XXXXXX";
120}
121
122tst_QMimeDatabase::tst_QMimeDatabase()
123 : m_temporaryDir(seedAndTemplate())
124{
125}
126
127void tst_QMimeDatabase::initTestCase()
128{
129 QLocale::setDefault(QLocale::c());
130 QVERIFY2(m_temporaryDir.isValid(), qPrintable(m_temporaryDir.errorString()));
131 QStandardPaths::setTestModeEnabled(true);
132 m_localMimeDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + "/mime";
133 if (QDir(m_localMimeDir).exists()) {
134 QVERIFY2(QDir(m_localMimeDir).removeRecursively(), qPrintable(m_localMimeDir + ": " + qt_error_string()));
135 }
136 QString errorMessage;
137
138#ifdef USE_XDG_DATA_DIRS
139 // Create a temporary "global" XDG data dir for later use
140 // It will initially contain a copy of freedesktop.org.xml
141 QVERIFY2(m_temporaryDir.isValid(),
142 ("Could not create temporary subdir: " + m_temporaryDir.errorString()).toUtf8());
143 const QDir here = QDir(m_temporaryDir.path());
144 m_globalXdgDir = m_temporaryDir.path() + QStringLiteral("/global");
145 const QString globalPackageDir = m_globalXdgDir + QStringLiteral("/mime/packages");
146 QVERIFY(here.mkpath(globalPackageDir));
147
148 qputenv(varName: "XDG_DATA_DIRS", value: QFile::encodeName(fileName: m_globalXdgDir));
149 qDebug() << "\nGlobal XDG_DATA_DIRS: " << m_globalXdgDir;
150
151 const QString freeDesktopXml = QStringLiteral("freedesktop.org.xml");
152 const QString xmlFileName = QLatin1String(RESOURCE_PREFIX "packages/") + freeDesktopXml;
153 const QString xmlTargetFileName = globalPackageDir + QLatin1Char('/') + freeDesktopXml;
154 QVERIFY2(copyResourceFile(xmlFileName, xmlTargetFileName, &errorMessage), qPrintable(errorMessage));
155#endif
156
157 m_testSuite = QFINDTESTDATA("s-m-i/tests");
158 if (m_testSuite.isEmpty())
159 qWarning(msg: "%s", qPrintable(testSuiteWarning()));
160
161 errorMessage = QString::fromLatin1(str: "Cannot find '%1'");
162 for (uint i = 0; i < sizeof additionalMimeFiles / sizeof additionalMimeFiles[0] - 1; i++) {
163 const QString resourceFilePath = QString::fromLatin1(RESOURCE_PREFIX) + QLatin1String(additionalMimeFiles[i]);
164 QVERIFY2(QFile::exists(resourceFilePath), qPrintable(errorMessage.arg(resourceFilePath)));
165 m_additionalMimeFileNames.append(t: QLatin1String(additionalMimeFiles[i]));
166 m_additionalMimeFilePaths.append(t: resourceFilePath);
167 }
168
169 initTestCaseInternal();
170 m_isUsingCacheProvider = !qEnvironmentVariableIsSet(varName: "QT_NO_MIME_CACHE");
171}
172
173void tst_QMimeDatabase::init()
174{
175 // clean up local data from previous runs
176 QDir(m_localMimeDir).removeRecursively();
177}
178
179void tst_QMimeDatabase::cleanupTestCase()
180{
181 QDir(m_localMimeDir).removeRecursively();
182}
183
184void tst_QMimeDatabase::mimeTypeForName()
185{
186 QMimeDatabase db;
187 QMimeType s0 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-zerosize"));
188 QVERIFY(s0.isValid());
189 QCOMPARE(s0.name(), QString::fromLatin1("application/x-zerosize"));
190 QCOMPARE(s0.comment(), QString::fromLatin1("empty document"));
191
192 QMimeType s0Again = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-zerosize"));
193 QCOMPARE(s0Again.name(), s0.name());
194
195 QMimeType s1 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/plain"));
196 QVERIFY(s1.isValid());
197 QCOMPARE(s1.name(), QString::fromLatin1("text/plain"));
198 //qDebug("Comment is %s", qPrintable(s1.comment()));
199
200 QMimeType krita = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-krita"));
201 QVERIFY(krita.isValid());
202
203 // Test <comment> parsing with application/rdf+xml which has the english comment after the other ones
204 QMimeType rdf = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/rdf+xml"));
205 QVERIFY(rdf.isValid());
206 QCOMPARE(rdf.comment(), QString::fromLatin1("RDF file"));
207
208 QMimeType bzip2 = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-bzip2"));
209 QVERIFY(bzip2.isValid());
210 QCOMPARE(bzip2.comment(), QString::fromLatin1("Bzip archive"));
211
212 QMimeType defaultMime = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/octet-stream"));
213 QVERIFY(defaultMime.isValid());
214 QVERIFY(defaultMime.isDefault());
215
216 QMimeType doesNotExist = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "foobar/x-doesnot-exist"));
217 QVERIFY(!doesNotExist.isValid());
218 QCOMPARE(doesNotExist.comment(), QString());
219 QCOMPARE(doesNotExist.aliases(), QStringList());
220
221 // TODO move to findByFile
222#ifdef Q_OS_LINUX
223 QString exePath = QStandardPaths::findExecutable(executableName: QLatin1String("ls"));
224 if (exePath.isEmpty())
225 qWarning() << "ls not found";
226 else {
227 const QString executableType = QString::fromLatin1(str: "application/x-executable");
228 const QString sharedLibType = QString::fromLatin1(str: "application/x-sharedlib");
229 //QTest::newRow("executable") << exePath << executableType;
230 QVERIFY(db.mimeTypeForFile(exePath).name() == executableType ||
231 db.mimeTypeForFile(exePath).name() == sharedLibType);
232 }
233#endif
234
235}
236
237void tst_QMimeDatabase::mimeTypeForFileName_data()
238{
239 QTest::addColumn<QString>(name: "fileName");
240 QTest::addColumn<QString>(name: "expectedMimeType");
241
242 QTest::newRow(dataTag: "text") << "textfile.txt" << "text/plain";
243 QTest::newRow(dataTag: "case-insensitive search") << "textfile.TxT" << "text/plain";
244
245 // Needs shared-mime-info > 0.91. Earlier versions wrote .Z to the mime.cache file...
246 //QTest::newRow("case-insensitive match on a non-lowercase glob") << "foo.z" << "application/x-compress";
247
248 QTest::newRow(dataTag: "case-sensitive uppercase match") << "textfile.C" << "text/x-c++src";
249 QTest::newRow(dataTag: "case-sensitive lowercase match") << "textfile.c" << "text/x-csrc";
250 QTest::newRow(dataTag: "case-sensitive long-extension match") << "foo.PS.gz" << "application/x-gzpostscript";
251 QTest::newRow(dataTag: "case-sensitive-only match") << "core" << "application/x-core";
252 QTest::newRow(dataTag: "case-sensitive-only match") << "Core" << "application/octet-stream"; // #198477
253
254 QTest::newRow(dataTag: "desktop file") << "foo.desktop" << "application/x-desktop";
255 QTest::newRow(dataTag: "old kdelnk file is x-desktop too") << "foo.kdelnk" << "application/x-desktop";
256 QTest::newRow(dataTag: "double-extension file") << "foo.tar.bz2" << "application/x-bzip-compressed-tar";
257 QTest::newRow(dataTag: "single-extension file") << "foo.bz2" << "application/x-bzip";
258 QTest::newRow(dataTag: ".doc should assume msword") << "somefile.doc" << "application/msword"; // #204139
259 QTest::newRow(dataTag: "glob that uses [] syntax, 1") << "Makefile" << "text/x-makefile";
260 QTest::newRow(dataTag: "glob that uses [] syntax, 2") << "makefile" << "text/x-makefile";
261 QTest::newRow(dataTag: "glob that ends with *, no extension") << "README" << "text/x-readme";
262 QTest::newRow(dataTag: "glob that ends with *, extension") << "README.foo" << "text/x-readme";
263 QTest::newRow(dataTag: "glob that ends with *, also matches *.txt. Higher weight wins.") << "README.txt" << "text/plain";
264 QTest::newRow(dataTag: "glob that ends with *, also matches *.nfo. Higher weight wins.") << "README.nfo" << "text/x-nfo";
265 // fdo bug 15436, needs shared-mime-info >= 0.40 (and this tests the globs2-parsing code).
266 QTest::newRow(dataTag: "glob that ends with *, also matches *.pdf. *.pdf has higher weight") << "README.pdf" << "application/pdf";
267 QTest::newRow(dataTag: "directory") << "/" << "inode/directory";
268 QTest::newRow(dataTag: "doesn't exist, no extension") << "IDontExist" << "application/octet-stream";
269 QTest::newRow(dataTag: "doesn't exist but has known extension") << "IDontExist.txt" << "text/plain";
270 QTest::newRow(dataTag: "empty") << "" << "application/octet-stream";
271}
272
273static inline QByteArray msgMimeTypeForFileNameFailed(const QList<QMimeType> &actual,
274 const QString &expected)
275{
276 QByteArray result = "Actual (";
277 foreach (const QMimeType &m, actual) {
278 result += m.name().toLocal8Bit();
279 result += ' ';
280 }
281 result += ") , expected: ";
282 result += expected.toLocal8Bit();
283 return result;
284}
285
286void tst_QMimeDatabase::mimeTypeForFileName()
287{
288 QFETCH(QString, fileName);
289 QFETCH(QString, expectedMimeType);
290 QMimeDatabase db;
291 QMimeType mime = db.mimeTypeForFile(fileName, mode: QMimeDatabase::MatchExtension);
292 QVERIFY(mime.isValid());
293 QCOMPARE(mime.name(), expectedMimeType);
294
295 QList<QMimeType> mimes = db.mimeTypesForFileName(fileName);
296 if (expectedMimeType == "application/octet-stream") {
297 QVERIFY(mimes.isEmpty());
298 } else {
299 QVERIFY2(!mimes.isEmpty(), msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData());
300 QVERIFY2(mimes.count() == 1, msgMimeTypeForFileNameFailed(mimes, expectedMimeType).constData());
301 QCOMPARE(mimes.first().name(), expectedMimeType);
302 }
303}
304
305void tst_QMimeDatabase::mimeTypesForFileName_data()
306{
307 QTest::addColumn<QString>(name: "fileName");
308 QTest::addColumn<QStringList>(name: "expectedMimeTypes");
309
310 QTest::newRow(dataTag: "txt, 1 hit") << "foo.txt" << (QStringList() << "text/plain");
311 QTest::newRow(dataTag: "txtfoobar, 0 hit") << "foo.foobar" << QStringList();
312 QTest::newRow(dataTag: "m, 2 hits") << "foo.m" << (QStringList() << "text/x-matlab" << "text/x-objcsrc");
313 QTest::newRow(dataTag: "sub, 3 hits") << "foo.sub" << (QStringList() << "text/x-microdvd" << "text/x-mpsub" << "text/x-subviewer");
314 QTest::newRow(dataTag: "non_ascii") << QString::fromUtf8(str: "AÄ°Ä°A.pdf") << (QStringList() << "application/pdf");
315}
316
317void tst_QMimeDatabase::mimeTypesForFileName()
318{
319 QFETCH(QString, fileName);
320 QFETCH(QStringList, expectedMimeTypes);
321 QMimeDatabase db;
322 QList<QMimeType> mimes = db.mimeTypesForFileName(fileName);
323 QStringList mimeNames;
324 foreach (const QMimeType &mime, mimes)
325 mimeNames.append(t: mime.name());
326 QCOMPARE(mimeNames, expectedMimeTypes);
327}
328
329void tst_QMimeDatabase::inheritance()
330{
331 QMimeDatabase db;
332
333 // All file-like mimetypes inherit from octet-stream
334 const QMimeType wordperfect = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/vnd.wordperfect"));
335 QVERIFY(wordperfect.isValid());
336 QCOMPARE(wordperfect.parentMimeTypes().join(QString::fromLatin1(",")), QString::fromLatin1("application/octet-stream"));
337 QVERIFY(wordperfect.inherits(QLatin1String("application/octet-stream")));
338
339 QVERIFY(db.mimeTypeForName(QString::fromLatin1("image/svg+xml-compressed")).inherits(QLatin1String("application/x-gzip")));
340
341 // Check that msword derives from ole-storage
342 const QMimeType msword = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/msword"));
343 QVERIFY(msword.isValid());
344 const QMimeType olestorage = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-ole-storage"));
345 QVERIFY(olestorage.isValid());
346 QVERIFY(msword.inherits(olestorage.name()));
347 QVERIFY(msword.inherits(QLatin1String("application/octet-stream")));
348
349 const QMimeType directory = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "inode/directory"));
350 QVERIFY(directory.isValid());
351 QCOMPARE(directory.parentMimeTypes().count(), 0);
352 QVERIFY(!directory.inherits(QLatin1String("application/octet-stream")));
353
354 // Check that text/x-patch knows that it inherits from text/plain (it says so explicitly)
355 const QMimeType plain = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/plain"));
356 const QMimeType derived = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/x-patch"));
357 QVERIFY(derived.isValid());
358 QCOMPARE(derived.parentMimeTypes().join(QString::fromLatin1(",")), plain.name());
359 QVERIFY(derived.inherits(QLatin1String("text/plain")));
360 QVERIFY(derived.inherits(QLatin1String("application/octet-stream")));
361
362 // Check that application/x-shellscript inherits from application/x-executable
363 // (Otherwise KRun cannot start shellscripts...)
364 // This is a test for multiple inheritance...
365 const QMimeType shellscript = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/x-shellscript"));
366 QVERIFY(shellscript.isValid());
367 QVERIFY(shellscript.inherits(QLatin1String("text/plain")));
368 QVERIFY(shellscript.inherits(QLatin1String("application/x-executable")));
369 const QStringList shellParents = shellscript.parentMimeTypes();
370 QVERIFY(shellParents.contains(QLatin1String("text/plain")));
371 QVERIFY(shellParents.contains(QLatin1String("application/x-executable")));
372 QCOMPARE(shellParents.count(), 2); // only the above two
373 const QStringList allShellAncestors = shellscript.allAncestors();
374 QVERIFY(allShellAncestors.contains(QLatin1String("text/plain")));
375 QVERIFY(allShellAncestors.contains(QLatin1String("application/x-executable")));
376 QVERIFY(allShellAncestors.contains(QLatin1String("application/octet-stream")));
377 // Must be least-specific last, i.e. breadth first.
378 QCOMPARE(allShellAncestors.last(), QString::fromLatin1("application/octet-stream"));
379
380 const QStringList allSvgAncestors = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "image/svg+xml")).allAncestors();
381 QCOMPARE(allSvgAncestors, QStringList() << QLatin1String("application/xml") << QLatin1String("text/plain") << QLatin1String("application/octet-stream"));
382
383 // Check that text/x-mrml knows that it inherits from text/plain (implicitly)
384 const QMimeType mrml = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/x-mrml"));
385 QVERIFY(mrml.isValid());
386 QVERIFY(mrml.inherits(QLatin1String("text/plain")));
387 QVERIFY(mrml.inherits(QLatin1String("application/octet-stream")));
388
389 // Check that msword-template inherits msword
390 const QMimeType mswordTemplate = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/msword-template"));
391 QVERIFY(mswordTemplate.isValid());
392 QVERIFY(mswordTemplate.inherits(QLatin1String("application/msword")));
393}
394
395void tst_QMimeDatabase::aliases()
396{
397 QMimeDatabase db;
398
399 const QMimeType canonical = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "application/xml"));
400 QVERIFY(canonical.isValid());
401
402 QMimeType resolvedAlias = db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "text/xml"));
403 QVERIFY(resolvedAlias.isValid());
404 QCOMPARE(resolvedAlias.name(), QString::fromLatin1("application/xml"));
405
406 QVERIFY(resolvedAlias.inherits(QLatin1String("application/xml")));
407 QVERIFY(canonical.inherits(QLatin1String("text/xml")));
408
409 // Test for kde bug 197346: does nspluginscan see that audio/mp3 already exists?
410 bool mustWriteMimeType = !db.mimeTypeForName(nameOrAlias: QString::fromLatin1(str: "audio/mp3")).isValid();
411 QVERIFY(!mustWriteMimeType);
412}
413
414void tst_QMimeDatabase::listAliases_data()
415{
416 QTest::addColumn<QString>(name: "inputMime");
417 QTest::addColumn<QString>(name: "expectedAliases");
418
419 QTest::newRow(dataTag: "csv") << "text/csv" << "text/x-csv,text/x-comma-separated-values";
420 QTest::newRow(dataTag: "xml") << "application/xml" << "text/xml";
421 QTest::newRow(dataTag: "xml2") << "text/xml" /* gets resolved to application/xml */ << "text/xml";
422 QTest::newRow(dataTag: "no_mime") << "message/news" << "";
423}
424
425void tst_QMimeDatabase::listAliases()
426{
427 QFETCH(QString, inputMime);
428 QFETCH(QString, expectedAliases);
429 QMimeDatabase db;
430 QStringList expectedAliasesList = expectedAliases.split(sep: ',', behavior: Qt::SkipEmptyParts);
431 expectedAliasesList.sort();
432 QMimeType mime = db.mimeTypeForName(nameOrAlias: inputMime);
433 QVERIFY(mime.isValid());
434 QStringList aliasList = mime.aliases();
435 aliasList.sort();
436 QCOMPARE(aliasList, expectedAliasesList);
437}
438
439void tst_QMimeDatabase::icons()
440{
441 QMimeDatabase db;
442 QMimeType directory = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "/"));
443 QCOMPARE(directory.name(), QString::fromLatin1("inode/directory"));
444 QCOMPARE(directory.iconName(), QString::fromLatin1("inode-directory"));
445 QCOMPARE(directory.genericIconName(), QString::fromLatin1("folder"));
446
447 QMimeType pub = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "foo.epub"), mode: QMimeDatabase::MatchExtension);
448 QCOMPARE(pub.name(), QString::fromLatin1("application/epub+zip"));
449 QCOMPARE(pub.iconName(), QString::fromLatin1("application-epub+zip"));
450 QCOMPARE(pub.genericIconName(), QString::fromLatin1("x-office-document"));
451}
452
453void tst_QMimeDatabase::comment()
454{
455 struct RestoreLocale
456 {
457 ~RestoreLocale() { QLocale::setDefault(QLocale::c()); }
458 } restoreLocale;
459
460 QLocale::setDefault(QLocale("de"));
461 QMimeDatabase db;
462 QMimeType directory = db.mimeTypeForName(QStringLiteral("inode/directory"));
463 QCOMPARE(directory.comment(), QStringLiteral("Ordner"));
464 QLocale::setDefault(QLocale("fr"));
465 QCOMPARE(directory.comment(), QStringLiteral("dossier"));
466}
467
468// In here we do the tests that need some content in a temporary file.
469// This could also be added to shared-mime-info's testsuite...
470void tst_QMimeDatabase::mimeTypeForFileWithContent()
471{
472 QMimeDatabase db;
473 QMimeType mime;
474
475 // Test a real PDF file.
476 // If we find x-matlab because it starts with '%' then we are not ordering by priority.
477 QTemporaryFile tempFile;
478 QVERIFY(tempFile.open());
479 QString tempFileName = tempFile.fileName();
480 tempFile.write(data: "%PDF-");
481 tempFile.close();
482 mime = db.mimeTypeForFile(fileName: tempFileName);
483 QCOMPARE(mime.name(), QString::fromLatin1("application/pdf"));
484 QFile file(tempFileName);
485 mime = db.mimeTypeForData(device: &file); // QIODevice ctor
486 QCOMPARE(mime.name(), QString::fromLatin1("application/pdf"));
487 // by name only, we cannot find the mimetype
488 mime = db.mimeTypeForFile(fileName: tempFileName, mode: QMimeDatabase::MatchExtension);
489 QVERIFY(mime.isValid());
490 QVERIFY(mime.isDefault());
491
492 // Test the case where the extension doesn't match the contents: extension wins
493 {
494 QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt"));
495 QVERIFY(txtTempFile.open());
496 txtTempFile.write(data: "%PDF-");
497 QString txtTempFileName = txtTempFile.fileName();
498 txtTempFile.close();
499 mime = db.mimeTypeForFile(fileName: txtTempFileName);
500 QCOMPARE(mime.name(), QString::fromLatin1("text/plain"));
501 // fast mode finds the same
502 mime = db.mimeTypeForFile(fileName: txtTempFileName, mode: QMimeDatabase::MatchExtension);
503 QCOMPARE(mime.name(), QString::fromLatin1("text/plain"));
504 }
505
506 // Now the case where extension differs from contents, but contents has >80 magic rule
507 // XDG spec says: contents wins. But we can't sniff all files...
508 {
509 QTemporaryFile txtTempFile(QDir::tempPath() + QLatin1String("/tst_QMimeDatabase_XXXXXX.txt"));
510 QVERIFY(txtTempFile.open());
511 txtTempFile.write(data: "<smil");
512 QString txtTempFileName = txtTempFile.fileName();
513 txtTempFile.close();
514 mime = db.mimeTypeForFile(fileName: txtTempFileName);
515 QCOMPARE(mime.name(), QString::fromLatin1("text/plain"));
516 mime = db.mimeTypeForFile(fileName: txtTempFileName, mode: QMimeDatabase::MatchContent);
517 QCOMPARE(mime.name(), QString::fromLatin1("application/smil+xml"));
518 }
519
520 // Test what happens with an incorrect path
521 mime = db.mimeTypeForFile(fileName: QString::fromLatin1(str: "file:///etc/passwd" /* incorrect code, use a path instead */));
522 QVERIFY(mime.isDefault());
523
524 // findByData when the device cannot be opened (e.g. a directory)
525 QFile dir("/");
526 mime = db.mimeTypeForData(device: &dir);
527 QVERIFY(mime.isDefault());
528}
529
530void tst_QMimeDatabase::mimeTypeForUrl()
531{
532 QMimeDatabase db;
533 QVERIFY(db.mimeTypeForUrl(QUrl::fromEncoded("http://foo/bar.png")).isDefault()); // HTTP can't know before downloading
534 QCOMPARE(db.mimeTypeForUrl(QUrl::fromEncoded("ftp://foo/bar.png")).name(), QString::fromLatin1("image/png"));
535 QCOMPARE(db.mimeTypeForUrl(QUrl::fromEncoded("ftp://foo/bar")).name(), QString::fromLatin1("application/octet-stream")); // unknown extension
536 QCOMPARE(db.mimeTypeForUrl(QUrl("mailto:something@example.com")).name(), QString::fromLatin1("application/octet-stream")); // unknown
537 QCOMPARE(db.mimeTypeForUrl(QUrl("mailto:something@polish.pl")).name(), QString::fromLatin1("application/octet-stream")); // unknown, NOT perl ;)
538}
539
540void tst_QMimeDatabase::mimeTypeForData_data()
541{
542 QTest::addColumn<QByteArray>(name: "data");
543 QTest::addColumn<QString>(name: "expectedMimeTypeName");
544
545 QTest::newRow(dataTag: "tnef data, needs smi >= 0.20") << QByteArray("\x78\x9f\x3e\x22") << "application/vnd.ms-tnef";
546 QTest::newRow(dataTag: "PDF magic") << QByteArray("%PDF-") << "application/pdf";
547 QTest::newRow(dataTag: "PHP, High-priority rule") << QByteArray("<?php") << "application/x-php";
548 QTest::newRow(dataTag: "diff\\t") << QByteArray("diff\t") << "text/x-patch";
549 QTest::newRow(dataTag: "unknown") << QByteArray("\001abc?}") << "application/octet-stream";
550}
551
552void tst_QMimeDatabase::mimeTypeForData()
553{
554 QFETCH(QByteArray, data);
555 QFETCH(QString, expectedMimeTypeName);
556
557 QMimeDatabase db;
558 QCOMPARE(db.mimeTypeForData(data).name(), expectedMimeTypeName);
559 QBuffer buffer(&data);
560 QCOMPARE(db.mimeTypeForData(&buffer).name(), expectedMimeTypeName);
561 QVERIFY(!buffer.isOpen()); // initial state was restored
562
563 QVERIFY(buffer.open(QIODevice::ReadOnly));
564 QCOMPARE(db.mimeTypeForData(&buffer).name(), expectedMimeTypeName);
565 QVERIFY(buffer.isOpen());
566 QCOMPARE(buffer.pos(), qint64(0));
567}
568
569void tst_QMimeDatabase::mimeTypeForFileAndContent_data()
570{
571 QTest::addColumn<QString>(name: "name");
572 QTest::addColumn<QByteArray>(name: "data");
573 QTest::addColumn<QString>(name: "expectedMimeTypeName");
574
575 QTest::newRow(dataTag: "plain text, no extension") << QString::fromLatin1(str: "textfile") << QByteArray("Hello world") << "text/plain";
576 QTest::newRow(dataTag: "plain text, unknown extension") << QString::fromLatin1(str: "textfile.foo") << QByteArray("Hello world") << "text/plain";
577 // Needs kde/mimetypes.xml
578 //QTest::newRow("plain text, doc extension") << QString::fromLatin1("textfile.doc") << QByteArray("Hello world") << "text/plain";
579
580 // If you get powerpoint instead, then you're hit by https://bugs.freedesktop.org/show_bug.cgi?id=435,
581 // upgrade to shared-mime-info >= 0.22
582 const QByteArray oleData("\320\317\021\340\241\261\032\341"); // same as \xD0\xCF\x11\xE0 \xA1\xB1\x1A\xE1
583 QTest::newRow(dataTag: "msword file, unknown extension") << QString::fromLatin1(str: "mswordfile") << oleData << "application/x-ole-storage";
584 QTest::newRow(dataTag: "excel file, found by extension") << QString::fromLatin1(str: "excelfile.xls") << oleData << "application/vnd.ms-excel";
585 QTest::newRow(dataTag: "text.xls, found by extension, user is in control") << QString::fromLatin1(str: "text.xls") << oleData << "application/vnd.ms-excel";
586}
587
588void tst_QMimeDatabase::mimeTypeForFileAndContent()
589{
590 QFETCH(QString, name);
591 QFETCH(QByteArray, data);
592 QFETCH(QString, expectedMimeTypeName);
593
594 QMimeDatabase db;
595 QCOMPARE(db.mimeTypeForFileNameAndData(name, data).name(), expectedMimeTypeName);
596
597 QBuffer buffer(&data);
598 QCOMPARE(db.mimeTypeForFileNameAndData(name, &buffer).name(), expectedMimeTypeName);
599 QVERIFY(!buffer.isOpen()); // initial state was restored
600
601 QVERIFY(buffer.open(QIODevice::ReadOnly));
602 QCOMPARE(db.mimeTypeForFileNameAndData(name, &buffer).name(), expectedMimeTypeName);
603 QVERIFY(buffer.isOpen());
604 QCOMPARE(buffer.pos(), qint64(0));
605}
606
607void tst_QMimeDatabase::allMimeTypes()
608{
609 QMimeDatabase db;
610 const QList<QMimeType> lst = db.allMimeTypes(); // does NOT include aliases
611 QVERIFY(!lst.isEmpty());
612
613 // Hardcoding this is the only way to check both providers find the same number of mimetypes.
614 QCOMPARE(lst.count(), 779);
615
616 foreach (const QMimeType &mime, lst) {
617 const QString name = mime.name();
618 QVERIFY(!name.isEmpty());
619 QCOMPARE(name.count(QLatin1Char('/')), 1);
620 const QMimeType lookedupMime = db.mimeTypeForName(nameOrAlias: name);
621 QVERIFY(lookedupMime.isValid());
622 QCOMPARE(lookedupMime.name(), name); // if this fails, you have an alias defined as a real mimetype too!
623 }
624}
625
626void tst_QMimeDatabase::suffixes_data()
627{
628 QTest::addColumn<QString>(name: "mimeType");
629 QTest::addColumn<QString>(name: "patterns");
630 QTest::addColumn<QString>(name: "preferredSuffix");
631
632 QTest::newRow(dataTag: "mimetype with a single pattern") << "application/pdf" << "*.pdf" << "pdf";
633 QTest::newRow(dataTag: "mimetype with multiple patterns") << "application/x-kpresenter" << "*.kpr;*.kpt" << "kpr";
634 QTest::newRow(dataTag: "jpeg") << "image/jpeg" << "*.jpe;*.jpg;*.jpeg" << "jpeg";
635 //if (KMimeType::sharedMimeInfoVersion() > KDE_MAKE_VERSION(0, 60, 0)) {
636 QTest::newRow(dataTag: "mimetype with many patterns") << "application/vnd.wordperfect" << "*.wp;*.wp4;*.wp5;*.wp6;*.wpd;*.wpp" << "wp";
637 //}
638 QTest::newRow(dataTag: "oasis text mimetype") << "application/vnd.oasis.opendocument.text" << "*.odt" << "odt";
639 QTest::newRow(dataTag: "oasis presentation mimetype") << "application/vnd.oasis.opendocument.presentation" << "*.odp" << "odp";
640 QTest::newRow(dataTag: "mimetype with multiple patterns") << "text/plain" << "*.asc;*.txt;*,v" << "txt";
641 QTest::newRow(dataTag: "mimetype with uncommon pattern") << "text/x-readme" << "README*" << QString();
642 QTest::newRow(dataTag: "mimetype with no patterns") << "application/x-ole-storage" << QString() << QString();
643 QTest::newRow(dataTag: "default_mimetype") << "application/octet-stream" << QString() << QString();
644}
645
646void tst_QMimeDatabase::suffixes()
647{
648 QFETCH(QString, mimeType);
649 QFETCH(QString, patterns);
650 QFETCH(QString, preferredSuffix);
651 QMimeDatabase db;
652 QMimeType mime = db.mimeTypeForName(nameOrAlias: mimeType);
653 QVERIFY(mime.isValid());
654 // Sort both lists; order is unreliable since shared-mime-info uses hashes internally.
655 QStringList expectedPatterns = patterns.split(sep: QLatin1Char(';'));
656 expectedPatterns.sort();
657 QStringList mimePatterns = mime.globPatterns();
658 mimePatterns.sort();
659 QCOMPARE(mimePatterns.join(QLatin1Char(';')), expectedPatterns.join(QLatin1Char(';')));
660 QCOMPARE(mime.preferredSuffix(), preferredSuffix);
661}
662
663void tst_QMimeDatabase::knownSuffix()
664{
665 QMimeDatabase db;
666 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar")), QString::fromLatin1("tar"));
667 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bz2")), QString::fromLatin1("bz2"));
668 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.bar.bz2")), QString::fromLatin1("bz2"));
669 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.tar.bz2")), QString::fromLatin1("tar.bz2"));
670 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.TAR")), QString::fromLatin1("TAR")); // preserve case
671 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.flatpakrepo")), QString::fromLatin1("flatpakrepo"));
672 QCOMPARE(db.suffixForFileName(QString::fromLatin1("foo.anim2")), QString()); // the glob is anim[0-9], no way to extract the extension without expensive regexp capturing
673}
674
675void tst_QMimeDatabase::symlinkToFifo() // QTBUG-48529
676{
677#ifdef Q_OS_UNIX
678 QTemporaryDir tempDir;
679 QVERIFY(tempDir.isValid());
680 const QString dir = tempDir.path();
681 const QString fifo = dir + "/fifo";
682 QCOMPARE(mkfifo(QFile::encodeName(fifo), 0006), 0);
683
684 QMimeDatabase db;
685 QCOMPARE(db.mimeTypeForFile(fifo).name(), QString::fromLatin1("inode/fifo"));
686
687 // Now make a symlink to the fifo
688 const QString link = dir + "/link";
689 QVERIFY(QFile::link(fifo, link));
690 QCOMPARE(db.mimeTypeForFile(link).name(), QString::fromLatin1("inode/fifo"));
691
692#else
693 QSKIP("This test requires pipes and symlinks");
694#endif
695}
696
697void tst_QMimeDatabase::findByFileName_data()
698{
699 QTest::addColumn<QString>(name: "filePath");
700 QTest::addColumn<QString>(name: "mimeTypeName");
701 QTest::addColumn<QString>(name: "xFail");
702
703 if (m_testSuite.isEmpty())
704 QSKIP("shared-mime-info test suite not available.");
705
706 const QString prefix = m_testSuite + QLatin1Char('/');
707 const QString fileName = prefix + QLatin1String("list");
708 QFile f(fileName);
709 QVERIFY2(f.open(QIODevice::ReadOnly|QIODevice::Text),
710 qPrintable(QString::fromLatin1("Cannot open %1: %2").arg(fileName, f.errorString())));
711
712 QByteArray line(1024, Qt::Uninitialized);
713
714 while (!f.atEnd()) {
715 int len = f.readLine(data: line.data(), maxlen: 1023);
716
717 if (len <= 2 || line.at(i: 0) == '#')
718 continue;
719
720 QString string = QString::fromLatin1(str: line.constData(), size: len - 1).trimmed();
721 QStringList list = string.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
722 QVERIFY(list.size() >= 2);
723
724 QString filePath = list.at(i: 0);
725 QString mimeTypeType = list.at(i: 1);
726 QString xFail;
727 if (list.size() >= 3)
728 xFail = list.at(i: 2);
729
730 QTest::newRow(dataTag: filePath.toLatin1().constData())
731 << QString(prefix + filePath)
732 << mimeTypeType << xFail;
733 }
734}
735
736void tst_QMimeDatabase::findByFileName()
737{
738 QFETCH(QString, filePath);
739 QFETCH(QString, mimeTypeName);
740 QFETCH(QString, xFail);
741
742 QMimeDatabase database;
743
744 //qDebug() << Q_FUNC_INFO << filePath;
745
746 const QMimeType resultMimeType(database.mimeTypeForFile(fileName: filePath, mode: QMimeDatabase::MatchExtension));
747 if (resultMimeType.isValid()) {
748 //qDebug() << Q_FUNC_INFO << "MIME type" << resultMimeType.name() << "has generic icon name" << resultMimeType.genericIconName() << "and icon name" << resultMimeType.iconName();
749
750// Loading icons depend on the icon theme, we can't enable this test
751#if 0
752 QCOMPARE(resultMimeType.genericIconName(), QIcon::fromTheme(resultMimeType.genericIconName()).name());
753 QVERIFY2(!QIcon::fromTheme(resultMimeType.genericIconName()).isNull(), qPrintable(resultMimeType.genericIconName()));
754 QVERIFY2(QIcon::hasThemeIcon(resultMimeType.genericIconName()), qPrintable(resultMimeType.genericIconName()));
755
756 QCOMPARE(resultMimeType.iconName(), QIcon::fromTheme(resultMimeType.iconName()).name());
757 QVERIFY2(!QIcon::fromTheme(resultMimeType.iconName()).isNull(), qPrintable(resultMimeType.iconName()));
758 QVERIFY2(QIcon::hasThemeIcon(resultMimeType.iconName()), qPrintable(resultMimeType.iconName()));
759#endif
760 }
761 const QString resultMimeTypeName = resultMimeType.name();
762 //qDebug() << Q_FUNC_INFO << "mimeTypeForFile() returned" << resultMimeTypeName;
763
764 const bool failed = resultMimeTypeName != mimeTypeName;
765 const bool shouldFail = (xFail.length() >= 1 && xFail.at(i: 0) == QLatin1Char('x'));
766 if (shouldFail != failed) {
767 // Results are ambiguous when multiple MIME types have the same glob
768 // -> accept the current result if the found MIME type actually
769 // matches the file's extension.
770 // TODO: a better file format in testfiles/list!
771 const QMimeType foundMimeType = database.mimeTypeForName(nameOrAlias: resultMimeTypeName);
772 QVERIFY2(resultMimeType == foundMimeType, qPrintable(resultMimeType.name() + QString::fromLatin1(" vs. ") + foundMimeType.name()));
773 if (foundMimeType.isValid()) {
774 const QString extension = QFileInfo(filePath).suffix();
775 //qDebug() << Q_FUNC_INFO << "globPatterns:" << foundMimeType.globPatterns() << "- extension:" << QString() + "*." + extension;
776 if (foundMimeType.globPatterns().contains(str: QString::fromLatin1(str: "*.") + extension))
777 return;
778 }
779 }
780 if (shouldFail) {
781 // Expected to fail
782 QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName));
783 } else {
784 QCOMPARE(resultMimeTypeName, mimeTypeName);
785 }
786
787 // Test QFileInfo overload
788 const QMimeType mimeForFileInfo = database.mimeTypeForFile(fileInfo: QFileInfo(filePath), mode: QMimeDatabase::MatchExtension);
789 QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName);
790
791 const QString suffix = database.suffixForFileName(fileName: filePath);
792 QVERIFY2(filePath.endsWith(suffix), qPrintable(filePath + " does not end with " + suffix));
793}
794
795void tst_QMimeDatabase::findByData_data()
796{
797 findByFileName_data();
798}
799
800void tst_QMimeDatabase::findByData()
801{
802 QFETCH(QString, filePath);
803 QFETCH(QString, mimeTypeName);
804 QFETCH(QString, xFail);
805
806 QMimeDatabase database;
807 QFile f(filePath);
808 QVERIFY(f.open(QIODevice::ReadOnly));
809 QByteArray data = f.read(maxlen: 16384);
810
811 const QString resultMimeTypeName = database.mimeTypeForData(data).name();
812 if (xFail.length() >= 2 && xFail.at(i: 1) == QLatin1Char('x')) {
813 // Expected to fail
814 QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName));
815 } else {
816 QCOMPARE(resultMimeTypeName.toLower(), mimeTypeName.toLower());
817 }
818
819 QFileInfo info(filePath);
820 QString mimeForInfo = database.mimeTypeForFile(fileInfo: info, mode: QMimeDatabase::MatchContent).name();
821 QCOMPARE(mimeForInfo, resultMimeTypeName);
822 QString mimeForFile = database.mimeTypeForFile(fileName: filePath, mode: QMimeDatabase::MatchContent).name();
823 QCOMPARE(mimeForFile, resultMimeTypeName);
824}
825
826void tst_QMimeDatabase::findByFile_data()
827{
828 findByFileName_data();
829}
830
831// Note: this test fails on "testcompress.z" when using a shared-mime-info older than 1.0.
832// This because of commit 0f9a506069c in shared-mime-info, which fixed the writing of
833// case-insensitive patterns into mime.cache.
834void tst_QMimeDatabase::findByFile()
835{
836 QFETCH(QString, filePath);
837 QFETCH(QString, mimeTypeName);
838 QFETCH(QString, xFail);
839
840 QMimeDatabase database;
841 const QString resultMimeTypeName = database.mimeTypeForFile(fileName: filePath).name();
842 //qDebug() << Q_FUNC_INFO << filePath << "->" << resultMimeTypeName;
843 if (xFail.length() >= 3 && xFail.at(i: 2) == QLatin1Char('x')) {
844 // Expected to fail
845 QVERIFY2(resultMimeTypeName != mimeTypeName, qPrintable(resultMimeTypeName));
846 } else {
847 QCOMPARE(resultMimeTypeName.toLower(), mimeTypeName.toLower());
848 }
849
850 // Test QFileInfo overload
851 const QMimeType mimeForFileInfo = database.mimeTypeForFile(fileInfo: QFileInfo(filePath));
852 QCOMPARE(mimeForFileInfo.name(), resultMimeTypeName);
853}
854
855
856void tst_QMimeDatabase::fromThreads()
857{
858 QThreadPool tp;
859 tp.setMaxThreadCount(20);
860 // Note that data-based tests cannot be used here (QTest::fetchData asserts).
861 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::mimeTypeForName);
862 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::aliases);
863 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::allMimeTypes);
864 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::icons);
865 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::inheritance);
866 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::knownSuffix);
867 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::mimeTypeForFileWithContent);
868 QtConcurrent::run(pool: &tp, object: this, fn: &tst_QMimeDatabase::allMimeTypes); // a second time
869 QVERIFY(tp.waitForDone(60000));
870}
871
872#if QT_CONFIG(process)
873
874enum {
875 UpdateMimeDatabaseTimeout = 4 * 60 * 1000 // 4min
876};
877
878static bool runUpdateMimeDatabase(const QString &path) // TODO make it a QMimeDatabase method?
879{
880 const QString umdCommand = QString::fromLatin1(str: "update-mime-database");
881 const QString umd = QStandardPaths::findExecutable(executableName: umdCommand);
882 if (umd.isEmpty()) {
883 qWarning(msg: "%s does not exist.", qPrintable(umdCommand));
884 return false;
885 }
886
887 QElapsedTimer timer;
888 QProcess proc;
889 proc.setProcessChannelMode(QProcess::MergedChannels); // silence output
890 qDebug().noquote() << "runUpdateMimeDatabase: running" << umd << path << "...";
891 timer.start();
892 proc.start(program: umd, arguments: QStringList(path));
893 if (!proc.waitForStarted()) {
894 qWarning(msg: "Cannot start %s: %s",
895 qPrintable(umd), qPrintable(proc.errorString()));
896 return false;
897 }
898 const bool success = proc.waitForFinished(msecs: UpdateMimeDatabaseTimeout);
899 qDebug().noquote() << "runUpdateMimeDatabase: done,"
900 << success << timer.elapsed() << "ms";
901 return true;
902}
903
904static bool waitAndRunUpdateMimeDatabase(const QString &path)
905{
906 QFileInfo mimeCacheInfo(path + QString::fromLatin1(str: "/mime.cache"));
907 if (mimeCacheInfo.exists()) {
908 // Wait until the beginning of the next second
909 while (mimeCacheInfo.lastModified().secsTo(QDateTime::currentDateTime()) == 0) {
910 QTest::qSleep(ms: 200);
911 }
912 }
913 return runUpdateMimeDatabase(path);
914}
915#endif // QT_CONFIG(process)
916
917static void checkHasMimeType(const QString &mimeType)
918{
919 QMimeDatabase db;
920 QVERIFY(db.mimeTypeForName(mimeType).isValid());
921
922 bool found = false;
923 foreach (const QMimeType &mt, db.allMimeTypes()) {
924 if (mt.name() == mimeType) {
925 found = true;
926 break;
927 }
928 }
929 QVERIFY(found);
930}
931
932static void ignoreInvalidMimetypeWarnings(const QString &mimeDir)
933{
934 const QByteArray basePath = QFile::encodeName(fileName: mimeDir) + "/packages/";
935 QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic1.xml\nInvalid magic rule value \"foo\"").constData());
936 QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic2.xml\nInvalid magic rule mask \"ffff\"").constData());
937 QTest::ignoreMessage(type: QtWarningMsg, message: ("QMimeDatabase: Error parsing " + basePath + "invalid-magic3.xml\nInvalid magic rule mask size \"0xffff\"").constData());
938}
939
940QT_BEGIN_NAMESPACE
941extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp
942QT_END_NAMESPACE
943
944void tst_QMimeDatabase::installNewGlobalMimeType()
945{
946#if !defined(USE_XDG_DATA_DIRS)
947 QSKIP("This test requires XDG_DATA_DIRS");
948#endif
949
950#if !QT_CONFIG(process)
951 QSKIP("This test requires QProcess support");
952#else
953 qmime_secondsBetweenChecks = 0;
954
955 QMimeDatabase db;
956 QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
957
958 const QString mimeDir = m_globalXdgDir + QLatin1String("/mime");
959 const QString destDir = mimeDir + QLatin1String("/packages/");
960 if (!QFileInfo(destDir).isDir())
961 QVERIFY(QDir(m_globalXdgDir).mkpath(destDir));
962
963 QString errorMessage;
964 for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) {
965 const QString destFile = destDir + m_additionalMimeFileNames.at(i);
966 QFile::remove(fileName: destFile);
967 QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage));
968 }
969 if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: mimeDir))
970 QSKIP("shared-mime-info not found, skipping mime.cache test");
971
972 if (!m_isUsingCacheProvider)
973 ignoreInvalidMimetypeWarnings(mimeDir);
974
975 QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
976 QString::fromLatin1("text/x-SuSE-ymu"));
977 QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
978 checkHasMimeType(mimeType: "text/x-suse-ymp");
979
980 // Test that a double-definition of a mimetype doesn't lead to sniffing ("conflicting globs").
981 const QString qmlTestFile = QLatin1String(RESOURCE_PREFIX "test.qml");
982 QVERIFY2(!qmlTestFile.isEmpty(),
983 qPrintable(QString::fromLatin1("Cannot find '%1' starting from '%2'").
984 arg("test.qml", QDir::currentPath())));
985 QCOMPARE(db.mimeTypeForFile(qmlTestFile).name(),
986 QString::fromLatin1("text/x-qml"));
987
988 {
989 QMimeType objcsrc = db.mimeTypeForName(QStringLiteral("text/x-objcsrc"));
990 QVERIFY(objcsrc.isValid());
991 QCOMPARE(objcsrc.globPatterns(), QStringList());
992 }
993
994 const QString fooTestFile = QLatin1String(RESOURCE_PREFIX "magic-and-hierarchy.foo");
995 QCOMPARE(db.mimeTypeForFile(fooTestFile).name(), QString::fromLatin1("application/foo"));
996
997 const QString fooTestFile2 = QLatin1String(RESOURCE_PREFIX "magic-and-hierarchy2.foo");
998 QCOMPARE(db.mimeTypeForFile(fooTestFile2).name(), QString::fromLatin1("application/vnd.qnx.bar-descriptor"));
999
1000 // Test if we can use the default comment
1001 {
1002 struct RestoreLocale
1003 {
1004 ~RestoreLocale() { QLocale::setDefault(QLocale::c()); }
1005 } restoreLocale;
1006
1007 QLocale::setDefault(QLocale("zh_CN"));
1008 QMimeType suseymp = db.mimeTypeForName(nameOrAlias: "text/x-suse-ymp");
1009 QVERIFY(suseymp.isValid());
1010 QCOMPARE(suseymp.comment(),
1011 QString::fromLatin1("YaST Meta Package"));
1012 }
1013
1014 // Now test removing the mimetype definitions again
1015 for (int i = 0; i < m_additionalMimeFileNames.size(); ++i)
1016 QFile::remove(fileName: destDir + m_additionalMimeFileNames.at(i));
1017 if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: mimeDir))
1018 QSKIP("shared-mime-info not found, skipping mime.cache test");
1019 QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
1020 QString::fromLatin1("application/octet-stream"));
1021 QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
1022#endif // QT_CONFIG(process)
1023}
1024
1025void tst_QMimeDatabase::installNewLocalMimeType()
1026{
1027#if !QT_CONFIG(process)
1028 QSKIP("This test requires QProcess support");
1029#else
1030 qmime_secondsBetweenChecks = 0;
1031
1032 QMimeDatabase db;
1033
1034 // Check that we're starting clean
1035 QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
1036 QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid());
1037
1038 const QString destDir = m_localMimeDir + QLatin1String("/packages/");
1039 QVERIFY(QDir().mkpath(destDir));
1040 QString errorMessage;
1041 for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) {
1042 const QString destFile = destDir + m_additionalMimeFileNames.at(i);
1043 QFile::remove(fileName: destFile);
1044 QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage));
1045 }
1046 if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: m_localMimeDir)) {
1047 const QString skipWarning = QStringLiteral("shared-mime-info not found, skipping mime.cache test (")
1048 + QDir::toNativeSeparators(pathName: m_localMimeDir) + QLatin1Char(')');
1049 QSKIP(qPrintable(skipWarning));
1050 }
1051
1052 if (!m_isUsingCacheProvider)
1053 ignoreInvalidMimetypeWarnings(mimeDir: m_localMimeDir);
1054
1055 QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
1056
1057 // These mimetypes have invalid magic, but still do exist.
1058 QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid());
1059 QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic2")).isValid());
1060 QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic3")).isValid());
1061
1062 QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
1063 QString::fromLatin1("text/x-SuSE-ymu"));
1064 QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
1065 QCOMPARE(db.mimeTypeForName(QLatin1String("text/x-SuSE-ymu")).comment(), QString("URL of a YaST Meta Package"));
1066 checkHasMimeType(mimeType: "text/x-suse-ymp");
1067
1068 { // QTBUG-85436
1069 QMimeType objcsrc = db.mimeTypeForName(QStringLiteral("text/x-objcsrc"));
1070 QVERIFY(objcsrc.isValid());
1071 QCOMPARE(objcsrc.globPatterns(), QStringList());
1072 }
1073
1074 // Test that a double-definition of a mimetype doesn't lead to sniffing ("conflicting globs").
1075 const QString qmlTestFile = QLatin1String(RESOURCE_PREFIX "test.qml");
1076 QVERIFY2(!qmlTestFile.isEmpty(),
1077 qPrintable(QString::fromLatin1("Cannot find '%1' starting from '%2'").
1078 arg("test.qml", QDir::currentPath())));
1079 QCOMPARE(db.mimeTypeForFile(qmlTestFile).name(),
1080 QString::fromLatin1("text/x-qml"));
1081
1082 // Now that we have two directories with mime definitions, check that everything still works
1083 inheritance();
1084 if (QTest::currentTestFailed())
1085 return;
1086
1087 aliases();
1088 if (QTest::currentTestFailed())
1089 return;
1090
1091 icons();
1092 if (QTest::currentTestFailed())
1093 return;
1094
1095 comment();
1096 if (QTest::currentTestFailed())
1097 return;
1098
1099 mimeTypeForFileWithContent();
1100 if (QTest::currentTestFailed())
1101 return;
1102
1103 mimeTypeForName();
1104 if (QTest::currentTestFailed())
1105 return;
1106
1107 // Now test removing local mimetypes
1108 for (int i = 1 ; i <= 3 ; ++i)
1109 QFile::remove(fileName: destDir + QStringLiteral("invalid-magic%1.xml").arg(a: i));
1110 if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(path: m_localMimeDir))
1111 QSKIP("shared-mime-info not found, skipping mime.cache test");
1112 QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid()); // deleted
1113 QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); // still present
1114
1115 // The user deletes the cache -> the XML provider makes things still work
1116 QFile::remove(fileName: m_localMimeDir + QString::fromLatin1(str: "/mime.cache"));
1117 QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid()); // deleted
1118 QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid()); // still present
1119
1120 // Finally, the user deletes the whole local dir
1121 QVERIFY2(QDir(m_localMimeDir).removeRecursively(), qPrintable(m_localMimeDir + ": " + qt_error_string()));
1122 QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
1123 QString::fromLatin1("application/octet-stream"));
1124 QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
1125#endif // QT_CONFIG(process)
1126}
1127
1128QTEST_GUILESS_MAIN(tst_QMimeDatabase)
1129

source code of qtbase/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp