diff --git a/autotests/usermetadatawritertest.cpp b/autotests/usermetadatawritertest.cpp index a246f62..76fef14 100644 --- a/autotests/usermetadatawritertest.cpp +++ b/autotests/usermetadatawritertest.cpp @@ -1,105 +1,112 @@ /* * Copyright (C) 2017 James D. Smith * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "usermetadatawritertest.h" #include "indexerextractortestsconfig.h" #include "usermetadata.h" #include #include #include #include #define TEST_FILENAME "writertest.txt" using namespace KFileMetaData; QString UserMetaDataWriterTest::testFilePath(const QString& fileName) const { return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QLatin1Char('/') + fileName; } void UserMetaDataWriterTest::initTestCase() { QFile testFile(testFilePath("plain_text_file.txt")); QFile writerTestFile(testFilePath(TEST_FILENAME)); QFile::copy(testFilePath("plain_text_file.txt"), testFilePath(TEST_FILENAME)); } void UserMetaDataWriterTest::test() { KFileMetaData::UserMetaData md(testFilePath(TEST_FILENAME)); QVERIFY(md.isSupported()); // Tags md.setTags(QStringList() << QStringLiteral("this/is/a/test/tag")); QCOMPARE(md.tags().at(0), QStringLiteral("this/is/a/test/tag")); md.setTags(QStringList()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.tags"))); // Rating md.setRating(3); QCOMPARE(md.rating(), 3); md.setRating(0); QVERIFY(!md.hasAttribute(QStringLiteral("baloo.rating"))); // Comment md.setUserComment(QStringLiteral("this is a test comment")); QCOMPARE(md.userComment(), QStringLiteral("this is a test comment")); md.setUserComment(QString()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.comment"))); // Origin url md.setOriginUrl(QUrl("http://this.is.a.test.website.local")); QCOMPARE(md.originUrl(), QUrl("http://this.is.a.test.website.local")); md.setOriginUrl(QUrl()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.origin.url"))); // Origin e-mail subject md.setOriginEmailSubject(QStringLiteral("this is a test e-mail subject")); QCOMPARE(md.originEmailSubject(), QStringLiteral("this is a test e-mail subject")); md.setOriginEmailSubject(QString()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.origin.email.subject"))); // Origin e-mail sender md.setOriginEmailSender(QStringLiteral("Blue Bear")); QCOMPARE(md.originEmailSender(), QStringLiteral("Blue Bear")); md.setOriginEmailSender(QString()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.origin.email.sender"))); // Origin e-mail message id md.setOriginEmailMessageId(QStringLiteral("19991231235959.52234.24C26516HHBTF1C4")); QCOMPARE(md.originEmailMessageId(), QStringLiteral("19991231235959.52234.24C26516HHBTF1C4")); md.setOriginEmailMessageId(QString()); QVERIFY(!md.hasAttribute(QStringLiteral("xdg.origin.email.message-id"))); // Attribute md.setAttribute(QStringLiteral("test.attribute"), QStringLiteral("attribute")); QCOMPARE(md.attribute(QStringLiteral("test.attribute")), QStringLiteral("attribute")); md.setAttribute(QStringLiteral("test.attribute"), QString()); QVERIFY(!md.hasAttribute(QStringLiteral("test.attribute"))); + + // Check for side effects of calling sequence + QVERIFY(!md.hasAttribute(QStringLiteral("test.check_contains"))); + md.setAttribute(QStringLiteral("test.check_contains"), QStringLiteral("dummy")); + QVERIFY(md.hasAttribute(QStringLiteral("test.check_contains"))); + md.setAttribute(QStringLiteral("test.check_contains"), QString()); + QVERIFY(!md.hasAttribute(QStringLiteral("test.check_contains"))); } void UserMetaDataWriterTest::cleanupTestCase() { QFile::remove(testFilePath(TEST_FILENAME)); } QTEST_GUILESS_MAIN(UserMetaDataWriterTest) diff --git a/src/xattr_p.h b/src/xattr_p.h index 1cc34ef..4e35eab 100644 --- a/src/xattr_p.h +++ b/src/xattr_p.h @@ -1,243 +1,244 @@ /* * This file is part of the KDE Baloo Project * Copyright (C) 2014 Raphael Kubo da Costa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef KFILEMETADATA_XATTR_P_H #define KFILEMETADATA_XATTR_P_H #include #include #include #include #include #include #if defined(Q_OS_LINUX) || defined(__GLIBC__) #include #include #if defined(Q_OS_ANDROID) || defined(Q_OS_LINUX) // attr/xattr.h is not available in the Android NDK so we are defining ENOATTR ourself #ifndef ENOATTR # define ENOATTR ENODATA /* No such attribute */ #endif #endif #include #elif defined(Q_OS_MAC) #include #include #include #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) #include #include #include #elif defined(Q_OS_WIN) #include #define ssize_t SSIZE_T #endif inline ssize_t k_getxattr(const QString& path, const QString& name, QString* value) { const QByteArray p = QFile::encodeName(path); const char* encodedPath = p.constData(); const QByteArray n = name.toUtf8(); const char* attributeName = n.constData(); // First get the size of the data we are going to get to reserve the right amount of space. #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) const ssize_t size = getxattr(encodedPath, attributeName, nullptr, 0); #elif defined(Q_OS_MAC) const ssize_t size = getxattr(encodedPath, attributeName, NULL, 0, 0, 0); #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) const ssize_t size = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, NULL, 0); #elif defined(Q_OS_WIN) const QString fullADSName = path + QLatin1Char(':') + name; HANDLE hFile = ::CreateFileW(reinterpret_cast(fullADSName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if(!hFile) return 0; LARGE_INTEGER lsize; lsize.LowPart = GetFileSize(hFile, (DWORD*)&lsize.HighPart); ssize_t size = (qint64)lsize.QuadPart; #else const ssize_t size = 0; #endif + if (!value) { + return size; + } + if (size <= 0) { - if (value) { - value->clear(); - } + value->clear(); return size; } QByteArray data(size, Qt::Uninitialized); #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) const ssize_t r = getxattr(encodedPath, attributeName, data.data(), size); #elif defined(Q_OS_MAC) const ssize_t r = getxattr(encodedPath, attributeName, data.data(), size, 0, 0); #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) const ssize_t r = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, data.data(), size); #elif defined(Q_OS_WIN) ssize_t r = 0; // should we care about attributes longer than 2GiB? - unix xattr are restricted to much lower values ::ReadFile(hFile, data.data(), size, (DWORD*)&r, NULL); CloseHandle(hFile); #else const ssize_t r = 0; #endif - if(value) *value = QString::fromUtf8(data); + *value = QString::fromUtf8(data); return r; } inline int k_setxattr(const QString& path, const QString& name, const QString& value) { const QByteArray p = QFile::encodeName(path); const char* encodedPath = p.constData(); const QByteArray n = name.toUtf8(); const char* attributeName = n.constData(); const QByteArray v = value.toUtf8(); const void* attributeValue = v.constData(); const size_t valueSize = v.size(); #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_setxattr)) return setxattr(encodedPath, attributeName, attributeValue, valueSize, 0); #elif defined(Q_OS_MAC) return setxattr(encodedPath, attributeName, attributeValue, valueSize, 0, 0); #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) const ssize_t count = extattr_set_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, attributeValue, valueSize); return count == -1 ? -1 : 0; #elif defined(Q_OS_WIN) const QString fullADSName = path + QLatin1Char(':') + name; HANDLE hFile = ::CreateFileW(reinterpret_cast(fullADSName.utf16()), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if(!hFile) return -1; DWORD count = 0; if(!::WriteFile(hFile, attributeValue, valueSize, &count, NULL)) { DWORD dw = GetLastError(); TCHAR msg[1024]; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &msg, 0, NULL ); qWarning() << "failed to write to ADS:" << msg; } CloseHandle(hFile); return count; #else return -1; #endif } inline int k_removexattr(const QString& path, const QString& name) { const QByteArray p = QFile::encodeName(path); const char* encodedPath = p.constData(); const QByteArray n = name.toUtf8(); const char* attributeName = n.constData(); #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_removexattr)) return removexattr(encodedPath, attributeName); #elif defined(Q_OS_MAC) return removexattr(encodedPath, attributeName, XATTR_NOFOLLOW ); #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) return extattr_delete_file (encodedPath, EXTATTR_NAMESPACE_USER, attributeName); #elif defined(Q_OS_WIN) const QString fullADSName = path + QLatin1Char(':') + name; int ret = (DeleteFileW(reinterpret_cast(fullADSName.utf16()))) ? 0 : -1; return ret; #else return -1; #endif } inline bool k_hasAttribute(const QString& path, const QString& name) { #if defined(Q_OS_WIN) // enumerate all streams: const QString streamName = QStringLiteral(":") + name + QStringLiteral(":$DATA"); HANDLE hFile = ::CreateFileW(reinterpret_cast(path.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if(!hFile) { return false; } FILE_STREAM_INFO* fi = new FILE_STREAM_INFO[256]; if(GetFileInformationByHandleEx(hFile, FileStreamInfo, fi, 256 * sizeof(FILE_STREAM_INFO))) { if(QString::fromUtf16((ushort*)fi->StreamName, fi->StreamNameLength / sizeof(ushort)) == streamName) { delete[] fi; CloseHandle(hFile); return true; } FILE_STREAM_INFO* p = fi; do { p = (FILE_STREAM_INFO*) ((char*)p + p->NextEntryOffset); if(QString::fromUtf16((ushort*)p->StreamName, p->StreamNameLength / sizeof(ushort)) == streamName) { delete[] fi; CloseHandle(hFile); return true; } } while(p->NextEntryOffset != NULL); } delete[] fi; CloseHandle(hFile); return false; #else - k_getxattr(path, name, nullptr); - return (errno != ENOATTR); + auto ret = k_getxattr(path, name, nullptr); + return (ret >= 0); #endif } inline bool k_isSupported(const QString& path) { - QString value; #if defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) - k_getxattr(path, QStringLiteral("user.test"), &value); - return (errno != ENOTSUP); + auto ret = k_getxattr(path, QStringLiteral("user.test"), nullptr); + return (ret >= 0) || (errno != ENOTSUP); #elif defined(Q_OS_WIN) QFileInfo f(path); const QString drive = QString(f.absolutePath().left(2)) + QStringLiteral("\\"); WCHAR szFSName[MAX_PATH]; DWORD dwVolFlags; ::GetVolumeInformationW(reinterpret_cast(drive.utf16()), NULL, 0, NULL, NULL, &dwVolFlags, szFSName, MAX_PATH); return ((dwVolFlags & FILE_NAMED_STREAMS) && _wcsicmp(szFSName, L"NTFS") == 0); #else return false; #endif } #endif // KFILEMETADATA_XATTR_P_H