diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index 40acaf663..1a55f01fa 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -384,14 +384,22 @@ bool AutoTypePlatformX11::isTopLevelWindow(Window window) int format; unsigned long nitems; unsigned long after; - unsigned char* data = nullptr; - int retVal = XGetWindowProperty(m_dpy, window, m_atomWmState, 0, 0, False, AnyPropertyType, &type, &format, + unsigned char* data = Q_NULLPTR; + int retVal = XGetWindowProperty(m_dpy, window, m_atomWmState, 0, 2, False, m_atomWmState, &type, &format, &nitems, &after, &data); - if (data) { + + bool result = false; + + if (retVal == 0 && data) { + if (type == m_atomWmState && format == 32 && nitems > 0) { + qint32 state = static_cast(*data); + result = (state != WithdrawnState); + } + XFree(data); } - return (retVal == 0) && type; + return result; } KeySym AutoTypePlatformX11::charToKeySym(const QChar& ch) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 2fd52d0b0..faa7cfacb 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -371,7 +371,6 @@ const QList& Entry::historyItems() const void Entry::addHistoryItem(Entry* entry) { Q_ASSERT(!entry->parent()); - Q_ASSERT(entry->uuid() == uuid()); m_history.append(entry); Q_EMIT modified(); diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index b0247f788..b45cefa6c 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -192,7 +192,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); if (headerHash != xmlReader.headerHash()) { raiseError("Header doesn't match hash"); - return nullptr; + return Q_NULLPTR; } } diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index 40087a633..a0d69afbd 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -778,6 +778,13 @@ Entry* KeePass2XmlReader::parseEntry(bool history) } Q_FOREACH (Entry* historyItem, historyItems) { + if (historyItem->uuid() != entry->uuid()) { + if (m_strictMode) { + raiseError("History element with different uuid"); + } else { + historyItem->setUuid(entry->uuid()); + } + } entry->addHistoryItem(historyItem); } diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 78a3a12a1..8f7350767 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -296,7 +296,7 @@ bool DatabaseTabWidget::saveDatabase(Database* db) DatabaseManagerStruct& dbStruct = m_dbList[db]; if (dbStruct.saveToFilename) { - QSaveFile saveFile(dbStruct.filePath); + QSaveFile saveFile(dbStruct.canonicalFilePath); if (saveFile.open(QIODevice::WriteOnly)) { m_writer.writeDatabase(&saveFile, db); if (m_writer.hasError()) { @@ -310,6 +310,11 @@ bool DatabaseTabWidget::saveDatabase(Database* db) return false; } } + else { + MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n" + + saveFile.errorString()); + return false; + } dbStruct.modified = false; updateTabName(db); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 3bca6fa31..4ff7a308e 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -493,7 +493,9 @@ void DatabaseWidget::deleteGroup() } bool inRecylceBin = Tools::hasChild(m_db->metadata()->recycleBin(), currentGroup); - if (inRecylceBin || !m_db->metadata()->recycleBinEnabled()) { + bool isRecycleBin = (currentGroup == m_db->metadata()->recycleBin()); + bool isRecycleBinSubgroup = Tools::hasChild(currentGroup, m_db->metadata()->recycleBin()); + if (inRecylceBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) { QMessageBox::StandardButton result = MessageBox::question( this, tr("Delete group?"), tr("Do you really want to delete the group \"%1\" for good?") @@ -871,8 +873,7 @@ bool DatabaseWidget::dbHasKey() const bool DatabaseWidget::canDeleteCurrentGroup() const { bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup(); - bool isRecycleBin = m_db->metadata()->recycleBin() == m_groupView->currentGroup(); - return !isRootGroup && !isRecycleBin; + return !isRootGroup; } bool DatabaseWidget::isInSearchMode() const diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 3d3565c5a..0aab991a3 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -19,6 +19,7 @@ #include "ui_EditWidgetIcons.h" #include +#include #include "core/Group.h" #include "core/Metadata.h" @@ -129,7 +130,10 @@ void EditWidgetIcons::addCustomIcon() QString filename = QFileDialog::getOpenFileName( this, tr("Select Image"), "", filter); if (!filename.isEmpty()) { - QImage image(filename); + QImageReader imageReader(filename); + // detect from content, otherwise reading fails if file extension is wrong + imageReader.setDecideFormatFromContent(true); + QImage image = imageReader.read(); if (!image.isNull()) { Uuid uuid = Uuid::random(); m_database->metadata()->addCustomIconScaled(uuid, image); @@ -139,7 +143,8 @@ void EditWidgetIcons::addCustomIcon() m_ui->customIconsView->setCurrentIndex(index); } else { - // TODO: show error + MessageBox::critical(this, tr("Error"), + tr("Can't read icon:").append("\n").append(imageReader.errorString())); } } } diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index d6fb12bea..4cb14c4de 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -111,6 +111,10 @@ void PasswordGeneratorWidget::sliderMoved() void PasswordGeneratorWidget::spinBoxChanged() { + if (m_updatingSpinBox) { + return; + } + // Interlock so that we don't update twice - this causes issues as the spinbox can go higher than slider m_updatingSpinBox = true; @@ -161,9 +165,39 @@ PasswordGenerator::GeneratorFlags PasswordGeneratorWidget::generatorFlags() void PasswordGeneratorWidget::updateGenerator() { + PasswordGenerator::CharClasses classes = charClasses(); + PasswordGenerator::GeneratorFlags flags = generatorFlags(); + + int minLength = 0; + if (flags.testFlag(PasswordGenerator::CharFromEveryGroup)) { + if (classes.testFlag(PasswordGenerator::LowerLetters)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::UpperLetters)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Numbers)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::SpecialCharacters)) { + minLength++; + } + } + minLength = qMax(minLength, 1); + + if (m_ui->spinBoxLength->value() < minLength) { + m_updatingSpinBox = true; + m_ui->spinBoxLength->setValue(minLength); + m_ui->sliderLength->setValue(minLength); + m_updatingSpinBox = false; + } + + m_ui->spinBoxLength->setMinimum(minLength); + m_ui->sliderLength->setMinimum(minLength); + m_generator->setLength(m_ui->spinBoxLength->value()); - m_generator->setCharClasses(charClasses()); - m_generator->setFlags(generatorFlags()); + m_generator->setCharClasses(classes); + m_generator->setFlags(flags); if (m_generator->isValid()) { QString password = m_generator->generatePassword(); diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index 60b7b90eb..9f0c87be7 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -18,6 +18,7 @@ #include "TestKeePass2Writer.h" #include +#include #include #include "config-keepassx-tests.h" diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index f04c54806..495b39acf 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -406,6 +406,8 @@ void TestKeePass2XmlReader::testBroken_data() QTest::newRow("BrokenGroupReference (not strict)") << "BrokenGroupReference" << false << false; QTest::newRow("BrokenDeletedObjects (strict)") << "BrokenDeletedObjects" << true << true; QTest::newRow("BrokenDeletedObjects (not strict)") << "BrokenDeletedObjects" << false << false; + QTest::newRow("BrokenDifferentEntryHistoryUuid (strict)") << "BrokenDifferentEntryHistoryUuid" << true << true; + QTest::newRow("BrokenDifferentEntryHistoryUuid (not strict)") << "BrokenDifferentEntryHistoryUuid" << false << false; } void TestKeePass2XmlReader::testEmptyUuids() @@ -486,6 +488,32 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); } +void TestKeePass2XmlReader::testRepairUuidHistoryItem() +{ + KeePass2XmlReader reader; + QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid"); + QVERIFY(QFile::exists(xmlFile)); + QScopedPointer db(reader.readDatabase(xmlFile)); + if (reader.hasError()) { + qWarning("Database read error: %s", qPrintable(reader.errorString())); + } + QVERIFY(!reader.hasError()); + + + + QList entries = db.data()->rootGroup()->entries(); + QCOMPARE(entries.size(), 1); + Entry* entry = entries.at(0); + + QList historyItems = entry->historyItems(); + QCOMPARE(historyItems.size(), 1); + Entry* historyItem = historyItems.at(0); + + QVERIFY(!entry->uuid().isNull()); + QVERIFY(!historyItem->uuid().isNull()); + QCOMPARE(historyItem->uuid(), entry->uuid()); +} + void TestKeePass2XmlReader::cleanupTestCase() { delete m_db; diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h index b9be7b553..ff83e2597 100644 --- a/tests/TestKeePass2XmlReader.h +++ b/tests/TestKeePass2XmlReader.h @@ -43,6 +43,7 @@ private Q_SLOTS: void testBroken_data(); void testEmptyUuids(); void testInvalidXmlChars(); + void testRepairUuidHistoryItem(); void cleanupTestCase(); private: diff --git a/tests/data/BrokenDifferentEntryHistoryUuid.xml b/tests/data/BrokenDifferentEntryHistoryUuid.xml new file mode 100644 index 000000000..c07f03aa7 --- /dev/null +++ b/tests/data/BrokenDifferentEntryHistoryUuid.xml @@ -0,0 +1,17 @@ + + + + + lmU+9n0aeESKZvcEze+bRg== + Test + + MTExMTExMTExMTExMTExMQ== + + + MjIyMjIyMjIyMjIyMjIyMg== + + + + + +