Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/browser/BrowserService.cpp')
-rw-r--r--src/browser/BrowserService.cpp744
1 files changed, 744 insertions, 0 deletions
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
new file mode 100644
index 000000000..fb89e8bcb
--- /dev/null
+++ b/src/browser/BrowserService.cpp
@@ -0,0 +1,744 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <QJsonArray>
+#include <QInputDialog>
+#include <QProgressDialog>
+#include <QMessageBox>
+#include "BrowserService.h"
+#include "BrowserSettings.h"
+#include "BrowserEntryConfig.h"
+#include "BrowserAccessControlDialog.h"
+#include "core/Database.h"
+#include "core/Group.h"
+#include "core/EntrySearcher.h"
+#include "core/Metadata.h"
+#include "core/Uuid.h"
+#include "core/PasswordGenerator.h"
+#include "gui/MainWindow.h"
+
+
+// de887cc3-0363-43b8-974b-5911b8816224
+static const unsigned char KEEPASSXCBROWSER_UUID_DATA[] = {
+ 0xde, 0x88, 0x7c, 0xc3, 0x03, 0x63, 0x43, 0xb8,
+ 0x97, 0x4b, 0x59, 0x11, 0xb8, 0x81, 0x62, 0x24
+};
+static const Uuid KEEPASSXCBROWSER_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSXCBROWSER_UUID_DATA), sizeof(KEEPASSXCBROWSER_UUID_DATA)));
+static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
+static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
+static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
+static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
+
+BrowserService::BrowserService(DatabaseTabWidget* parent) :
+ m_dbTabWidget(parent),
+ m_dialogActive(false)
+{
+ connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), this, SLOT(activateDatabaseChanged(DatabaseWidget*)));
+}
+
+bool BrowserService::isDatabaseOpened() const
+{
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ return dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode;
+
+}
+
+bool BrowserService::openDatabase(bool triggerUnlock)
+{
+ if (!BrowserSettings::unlockDatabase()) {
+ return false;
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ return true;
+ }
+
+ if (triggerUnlock) {
+ KEEPASSXC_MAIN_WINDOW->bringToFront();
+ }
+
+ return false;
+}
+
+void BrowserService::lockDatabase()
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ dbWidget->lock();
+ }
+}
+
+QString BrowserService::getDatabaseRootUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return QString();
+ }
+
+ return rootGroup->uuid().toHex();
+}
+
+QString BrowserService::getDatabaseRecycleBinUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* recycleBin = db->metadata()->recycleBin();
+ if (!recycleBin) {
+ return QString();
+ }
+ return recycleBin->uuid().toHex();
+}
+
+Entry* BrowserService::getConfigEntry(bool create)
+{
+ Entry* entry = nullptr;
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
+ if (!entry && create) {
+ entry = new Entry();
+ entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
+ entry->setUuid(KEEPASSXCBROWSER_UUID);
+ entry->setAutoTypeEnabled(false);
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+
+ if (entry && entry->group() == db->metadata()->recycleBin()) {
+ if (!create) {
+ return nullptr;
+ } else {
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+ }
+
+ return entry;
+}
+
+QString BrowserService::storeKey(const QString& key)
+{
+ QString id;
+
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "storeKey", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QString, id),
+ Q_ARG(const QString&, key));
+ return id;
+ }
+
+ Entry* config = getConfigEntry(true);
+ if (!config) {
+ return {};
+ }
+
+ bool contains;
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+
+ do {
+ QInputDialog keyDialog;
+ keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
+ keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
+ "If you would like to allow it access to your KeePassXC database,\n"
+ "give it a unique name to identify and accept it."));
+ keyDialog.setOkButtonText(tr("Save and allow access"));
+ keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
+ keyDialog.show();
+ keyDialog.activateWindow();
+ keyDialog.raise();
+ auto ok = keyDialog.exec();
+
+ id = keyDialog.textValue();
+
+ if (ok != QDialog::Accepted || id.isEmpty()) {
+ return {};
+ }
+
+ contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+ if (contains) {
+ dialogResult = QMessageBox::warning(nullptr, tr("KeePassXC: Overwrite existing key?"),
+ tr("A shared encryption key with the name \"%1\" "
+ "already exists.\nDo you want to overwrite it?")
+ .arg(id),
+ QMessageBox::Yes | QMessageBox::No);
+ }
+ } while (contains && dialogResult == QMessageBox::No);
+
+ config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
+ return id;
+}
+
+QString BrowserService::getKey(const QString& id)
+{
+ Entry* config = getConfigEntry();
+ if (!config) {
+ return QString();
+ }
+
+ return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+}
+
+QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ QJsonArray result;
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "findMatchingEntries", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QJsonArray, result),
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, url),
+ Q_ARG(const QString&, submitUrl),
+ Q_ARG(const QString&, realm));
+ return result;
+ }
+
+ const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+
+ // Check entries for authorization
+ QList<Entry*> pwEntriesToConfirm;
+ QList<Entry*> pwEntries;
+ for (Entry* entry : searchEntries(url)) {
+ switch (checkAccess(entry, host, submitHost, realm)) {
+ case Denied:
+ continue;
+
+ case Unknown:
+ if (alwaysAllowAccess) {
+ pwEntries.append(entry);
+ } else {
+ pwEntriesToConfirm.append(entry);
+ }
+ break;
+
+ case Allowed:
+ pwEntries.append(entry);
+ break;
+ }
+ }
+
+ // Confirm entries
+ if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
+ pwEntries.append(pwEntriesToConfirm);
+ }
+
+ if (pwEntries.isEmpty()) {
+ return QJsonArray();
+ }
+
+ // Sort results
+ pwEntries = sortEntries(pwEntries, host, submitUrl);
+
+ // Fill the list
+ for (Entry* entry : pwEntries) {
+ result << prepareEntry(entry);
+ }
+
+ return result;
+}
+
+void BrowserService::addEntry(const QString&, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ Group* group = findCreateAddEntryGroup();
+ if (!group) {
+ return;
+ }
+
+ Entry* entry = new Entry();
+ entry->setUuid(Uuid::random());
+ entry->setTitle(QUrl(url).host());
+ entry->setUrl(url);
+ entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->setGroup(group);
+
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+ BrowserEntryConfig config;
+ config.allow(host);
+
+ if (!submitHost.isEmpty()) {
+ config.allow(submitHost);
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+}
+
+void BrowserService::updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url)
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "updateEntry", Qt::BlockingQueuedConnection,
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, uuid),
+ Q_ARG(const QString&, login),
+ Q_ARG(const QString&, password),
+ Q_ARG(const QString&, url));
+ }
+
+ Database* db = getDatabase();
+ if (!db) {
+ return;
+ }
+
+ Entry* entry = db->resolveEntry(Uuid::fromHex(uuid));
+ if (!entry) {
+ return;
+ }
+
+ QString username = entry->username();
+ if (username.isEmpty()) {
+ return;
+ }
+
+ if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+ if (!BrowserSettings::alwaysAllowUpdate()) {
+ dialogResult = QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
+ tr("Do you want to update the information in %1 - %2?")
+ .arg(QUrl(url).host()).arg(username),
+ QMessageBox::Yes|QMessageBox::No);
+ }
+
+ if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
+ entry->beginUpdate();
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->endUpdate();
+ }
+ }
+}
+
+QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname)
+{
+ QList<Entry*> entries;
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return entries;
+ }
+
+ for (Entry* entry : EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
+ QString title = entry->title();
+ QString url = entry->url();
+
+ // Filter to match hostname in Title and Url fields
+ if ((!title.isEmpty() && hostname.contains(title))
+ || (!url.isEmpty() && hostname.contains(url))
+ || (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
+ || (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) {
+ entries.append(entry);
+ }
+ }
+
+ return entries;
+}
+
+QList<Entry*> BrowserService::searchEntries(const QString& text)
+{
+ // Get the list of databases to search
+ QList<Database*> databases;
+ if (BrowserSettings::searchInAllDatabases()) {
+ const int count = m_dbTabWidget->count();
+ for (int i = 0; i < count; ++i) {
+ if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
+ if (Database* db = dbWidget->database()) {
+ databases << db;
+ }
+ }
+ }
+ } else if (Database* db = getDatabase()) {
+ databases << db;
+ }
+
+ // Search entries matching the hostname
+ QString hostname = QUrl(text).host();
+ QList<Entry*> entries;
+ do {
+ for (Database* db : databases) {
+ entries << searchEntries(db, hostname);
+ }
+ } while (entries.isEmpty() && removeFirstDomain(hostname));
+
+ return entries;
+}
+
+void BrowserService::removeSharedEncryptionKeys()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Entry* entry = getConfigEntry();
+ if (!entry) {
+ QMessageBox::information(0, tr("KeePassXC: Settings not available!"),
+ tr("The active database does not contain a settings entry."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ QStringList keysToRemove;
+ for (const QString& key : entry->attributes()->keys()) {
+ if (key.startsWith(ASSOCIATE_KEY_PREFIX)) {
+ keysToRemove << key;
+ }
+ }
+
+ if (keysToRemove.isEmpty()) {
+ QMessageBox::information(0, tr("KeePassXC: No keys found"),
+ tr("No shared encryption keys found in KeePassXC Settings."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ entry->beginUpdate();
+ for (const QString& key : keysToRemove) {
+ entry->attributes()->remove(key);
+ }
+ entry->endUpdate();
+
+ const int count = keysToRemove.count();
+ QMessageBox::information(0, tr("KeePassXC: Removed keys from database"),
+ tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
+ QMessageBox::Ok);
+
+}
+
+void BrowserService::removeStoredPermissions()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Database* db = m_dbTabWidget->currentDatabaseWidget()->database();
+ if (!db) {
+ return;
+ }
+
+ QList<Entry*> entries = db->rootGroup()->entriesRecursive();
+
+ QProgressDialog progress(tr("Removing stored permissions…"), tr("Abort"), 0, entries.count());
+ progress.setWindowModality(Qt::WindowModal);
+
+ uint counter = 0;
+ for (Entry* entry : entries) {
+ if (progress.wasCanceled()) {
+ return;
+ }
+
+ if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) {
+ entry->beginUpdate();
+ entry->attributes()->remove(KEEPASSXCBROWSER_NAME);
+ entry->endUpdate();
+ ++counter;
+ }
+ progress.setValue(progress.value() + 1);
+ }
+ progress.reset();
+
+ if (counter > 0) {
+ QMessageBox::information(0, tr("KeePassXC: Removed permissions"),
+ tr("Successfully removed permissions from %n entry(s).", "", counter),
+ QMessageBox::Ok);
+ } else {
+ QMessageBox::information(0, tr("KeePassXC: No entry with permissions found!"),
+ tr("The active database does not contain an entry with permissions."),
+ QMessageBox::Ok);
+ }
+}
+
+QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
+{
+ QUrl url(entryUrl);
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+
+ const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
+ const QString baseSubmitUrl = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ QMultiMap<int, const Entry*> priorities;
+ for (const Entry* entry : pwEntries) {
+ priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
+ }
+
+ QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
+ std::sort(pwEntries.begin(), pwEntries.end(), [&priorities, &field](const Entry* left, const Entry* right) {
+ int res = priorities.key(left) - priorities.key(right);
+ if (res == 0) {
+ return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
+ }
+ return res < 0;
+ });
+
+ return pwEntries;
+}
+
+bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm)
+{
+ if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
+ return false;
+ }
+
+ m_dialogActive = true;
+ BrowserAccessControlDialog accessControlDialog;
+ accessControlDialog.setUrl(url);
+ accessControlDialog.setItems(pwEntriesToConfirm);
+
+ int res = accessControlDialog.exec();
+ if (accessControlDialog.remember()) {
+ for (Entry* entry : pwEntriesToConfirm) {
+ BrowserEntryConfig config;
+ config.load(entry);
+ if (res == QDialog::Accepted) {
+ config.allow(host);
+ if (!submitHost.isEmpty() && host != submitHost)
+ config.allow(submitHost);
+ } else if (res == QDialog::Rejected) {
+ config.deny(host);
+ if (!submitHost.isEmpty() && host != submitHost) {
+ config.deny(submitHost);
+ }
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+ }
+ }
+
+ m_dialogActive = false;
+ if (res == QDialog::Accepted) {
+ return true;
+ }
+
+ return false;
+}
+
+QJsonObject BrowserService::prepareEntry(const Entry* entry)
+{
+ QJsonObject res;
+ res["login"] = entry->resolveMultiplePlaceholders(entry->username());
+ res["password"] = entry->resolveMultiplePlaceholders(entry->password());
+ res["name"] = entry->resolveMultiplePlaceholders(entry->title());
+ res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
+
+ if (BrowserSettings::supportKphFields()) {
+ const EntryAttributes* attr = entry->attributes();
+ QJsonArray stringFields;
+ for (const QString& key : attr->keys()) {
+ if (key.startsWith(QLatin1String("KPH: "))) {
+ QJsonObject sField;
+ sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
+ stringFields << sField;
+ }
+ }
+ res["stringFields"] = stringFields;
+ }
+ return res;
+}
+
+BrowserService::Access BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
+{
+ BrowserEntryConfig config;
+ if (!config.load(entry)) {
+ return Unknown;
+ }
+ if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
+ return Allowed;
+ }
+ if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
+ return Denied;
+ }
+ if (!realm.isEmpty() && config.realm() != realm) {
+ return Denied;
+ }
+ return Unknown;
+}
+
+Group* BrowserService::findCreateAddEntryGroup()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return nullptr;
+ }
+
+ const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); //TODO: setting to decide where new keys are created
+
+ for (const Group* g : rootGroup->groupsRecursive(true)) {
+ if (g->name() == groupName) {
+ return db->resolveGroup(g->uuid());
+ }
+ }
+
+ Group* group = new Group();
+ group->setUuid(Uuid::random());
+ group->setName(groupName);
+ group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ group->setParent(rootGroup);
+ return group;
+}
+
+int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
+{
+ QUrl url(entry->url());
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+ const QString entryURL = url.toString(QUrl::StripTrailingSlash);
+ const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ if (submitUrl == entryURL) {
+ return 100;
+ }
+ if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
+ return 90;
+ }
+ if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
+ return 80;
+ }
+ if (entryURL == host) {
+ return 70;
+ }
+ if (entryURL == baseSubmitUrl) {
+ return 60;
+ }
+ if (entryURL.startsWith(submitUrl)) {
+ return 50;
+ }
+ if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
+ return 40;
+ }
+ if (submitUrl.startsWith(entryURL)) {
+ return 30;
+ }
+ if (submitUrl.startsWith(baseEntryURL)) {
+ return 20;
+ }
+ if (entryURL.startsWith(host)) {
+ return 10;
+ }
+ if (host.startsWith(entryURL)) {
+ return 5;
+ }
+ return 0;
+}
+
+bool BrowserService::matchUrlScheme(const QString& url)
+{
+ QUrl address(url);
+ return !address.scheme().isEmpty();
+}
+
+bool BrowserService::removeFirstDomain(QString& hostname)
+{
+ int pos = hostname.indexOf(".");
+ if (pos < 0) {
+ return false;
+ }
+
+ // Don't remove the second-level domain if it's the only one
+ if (hostname.count(".") > 1) {
+ hostname = hostname.mid(pos + 1);
+ return !hostname.isEmpty();
+ }
+
+ // Nothing removed
+ return false;
+}
+
+Database* BrowserService::getDatabase()
+{
+ if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
+ if (Database* db = dbWidget->database()) {
+ return db;
+ }
+ }
+ return nullptr;
+}
+
+void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseLocked();
+ }
+}
+
+void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseUnlocked();
+ }
+}
+
+void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ auto currentMode = dbWidget->currentMode();
+ if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
+ emit databaseUnlocked();
+ } else {
+ emit databaseLocked();
+ }
+ }
+}