File indexing completed on 2024-05-12 04:58:19

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2017 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "autofillmanager.h"
0019 #include "ui_autofillmanager.h"
0020 #include "autofill.h"
0021 #include "passwordmanager.h"
0022 #include "passwordbackends/passwordbackend.h"
0023 #include "mainapplication.h"
0024 #include "settings.h"
0025 #include "qztools.h"
0026 #include "sqldatabase.h"
0027 
0028 #include <QUrl>
0029 #include <QMenu>
0030 #include <QTimer>
0031 #include <QMessageBox>
0032 #include <QInputDialog>
0033 #include <QFileDialog>
0034 #include <QClipboard>
0035 
0036 AutoFillManager::AutoFillManager(QWidget* parent)
0037     : QWidget(parent)
0038     , ui(new Ui::AutoFillManager)
0039     , m_passwordManager(mApp->autoFill()->passwordManager())
0040     , m_passwordsShown(false)
0041 {
0042     ui->setupUi(this);
0043     if (isRightToLeft()) {
0044         ui->treePass->headerItem()->setTextAlignment(0, Qt::AlignRight | Qt::AlignVCenter);
0045         ui->treePass->headerItem()->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
0046         ui->treePass->headerItem()->setTextAlignment(2, Qt::AlignRight | Qt::AlignVCenter);
0047         ui->treePass->setLayoutDirection(Qt::LeftToRight);
0048         ui->treeExcept->setLayoutDirection(Qt::LeftToRight);
0049     }
0050 
0051     connect(ui->removePass, &QAbstractButton::clicked, this, &AutoFillManager::removePass);
0052     connect(ui->removeAllPass, &QAbstractButton::clicked, this, &AutoFillManager::removeAllPass);
0053     connect(ui->editPass, &QAbstractButton::clicked, this, &AutoFillManager::editPass);
0054     connect(ui->showPasswords, &QAbstractButton::clicked, this, &AutoFillManager::showPasswords);
0055     connect(ui->search, &QLineEdit::textChanged, ui->treePass, &TreeWidget::filterString);
0056     connect(ui->changeBackend, &QAbstractButton::clicked, this, &AutoFillManager::changePasswordBackend);
0057     connect(ui->backendOptions, &QAbstractButton::clicked, this, &AutoFillManager::showBackendOptions);
0058     connect(m_passwordManager, &PasswordManager::passwordBackendChanged, this, &AutoFillManager::currentPasswordBackendChanged);
0059 
0060     connect(ui->removeExcept, &QAbstractButton::clicked, this, &AutoFillManager::removeExcept);
0061     connect(ui->removeAllExcept, &QAbstractButton::clicked, this, &AutoFillManager::removeAllExcept);
0062 
0063     ui->treePass->setContextMenuPolicy(Qt::CustomContextMenu);
0064     connect(ui->treePass, &TreeWidget::customContextMenuRequested, this, &AutoFillManager::passwordContextMenu);
0065 
0066     auto* menu = new QMenu(this);
0067     menu->addAction(tr("Import Passwords from File..."), this, &AutoFillManager::importPasswords);
0068     menu->addAction(tr("Export Passwords to File..."), this, &AutoFillManager::exportPasswords);
0069     ui->importExport->setMenu(menu);
0070     ui->search->setPlaceholderText(tr("Search"));
0071 
0072     // Password backends
0073     ui->currentBackend->setText(QSL("<b>%1</b>").arg(m_passwordManager->activeBackend()->name()));
0074     ui->backendOptions->setVisible(m_passwordManager->activeBackend()->hasSettings());
0075 
0076     // Load passwords
0077     QTimer::singleShot(0, this, &AutoFillManager::loadPasswords);
0078 }
0079 
0080 void AutoFillManager::loadPasswords()
0081 {
0082     ui->showPasswords->setText(tr("Show Passwords"));
0083     m_passwordsShown = false;
0084 
0085     const QVector<PasswordEntry> allEntries = mApp->autoFill()->getAllFormData();
0086 
0087     ui->treePass->clear();
0088     for (const PasswordEntry &entry : allEntries) {
0089         auto* item = new QTreeWidgetItem(ui->treePass);
0090         item->setText(0, entry.host);
0091         item->setText(1, entry.username);
0092         item->setText(2, QSL("*****"));
0093 
0094         QVariant v;
0095         v.setValue(entry);
0096         item->setData(0, Qt::UserRole + 10, v);
0097         ui->treePass->addTopLevelItem(item);
0098     }
0099 
0100     QSqlQuery query(SqlDatabase::instance()->database());
0101     query.exec(QSL("SELECT server, id FROM autofill_exceptions"));
0102     ui->treeExcept->clear();
0103     while (query.next()) {
0104         auto* item = new QTreeWidgetItem(ui->treeExcept);
0105         item->setText(0, query.value(0).toString());
0106         item->setData(0, Qt::UserRole + 10, query.value(1).toString());
0107         ui->treeExcept->addTopLevelItem(item);
0108     }
0109 
0110     ui->treePass->sortByColumn(-1, Qt::AscendingOrder);
0111     ui->treeExcept->sortByColumn(-1, Qt::AscendingOrder);
0112 }
0113 
0114 void AutoFillManager::changePasswordBackend()
0115 {
0116     QHash<QString, PasswordBackend*> backends = m_passwordManager->availableBackends();
0117     QStringList items;
0118 
0119     int current = 0;
0120 
0121     QHashIterator<QString, PasswordBackend*> i(backends);
0122     while (i.hasNext()) {
0123         i.next();
0124         if (i.value() == m_passwordManager->activeBackend()) {
0125             current = items.size();
0126         }
0127         items << i.value()->name();
0128     }
0129 
0130     QString item = QInputDialog::getItem(this, tr("Change backend..."), tr("Change backend:"), items, current, false);
0131 
0132     // Switch backends
0133     if (!item.isEmpty()) {
0134         PasswordBackend* backend = nullptr;
0135 
0136         QHashIterator<QString, PasswordBackend*> i(backends);
0137         while (i.hasNext()) {
0138             i.next();
0139             if (i.value()->name() == item) {
0140                 backend = i.value();
0141                 break;
0142             }
0143         }
0144 
0145         if (backend) {
0146             m_passwordManager->switchBackend(backends.key(backend));
0147         }
0148     }
0149 }
0150 
0151 void AutoFillManager::showBackendOptions()
0152 {
0153     PasswordBackend* backend = m_passwordManager->activeBackend();
0154 
0155     if (backend->hasSettings()) {
0156         backend->showSettings(this);
0157     }
0158 }
0159 
0160 void AutoFillManager::showPasswords()
0161 {
0162     if (m_passwordsShown) {
0163         for (int i = 0; i < ui->treePass->topLevelItemCount(); i++) {
0164             QTreeWidgetItem* item = ui->treePass->topLevelItem(i);
0165             if (!item) {
0166                 continue;
0167             }
0168             item->setText(2, QSL("*****"));
0169         }
0170 
0171         ui->showPasswords->setText(tr("Show Passwords"));
0172         m_passwordsShown = false;
0173 
0174         return;
0175     }
0176 
0177     m_passwordsShown = true;
0178 
0179     int result = QMessageBox::question(this, tr("Show Passwords"), tr("Are you sure that you want to show all passwords?"),
0180                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
0181     if (result != QMessageBox::Yes) {
0182         return;
0183     }
0184 
0185     for (int i = 0; i < ui->treePass->topLevelItemCount(); i++) {
0186         QTreeWidgetItem* item = ui->treePass->topLevelItem(i);
0187         if (!item) {
0188             continue;
0189         }
0190 
0191         item->setText(2, item->data(0, Qt::UserRole + 10).value<PasswordEntry>().password);
0192     }
0193 
0194     ui->showPasswords->setText(tr("Hide Passwords"));
0195 }
0196 
0197 void AutoFillManager::copyPassword()
0198 {
0199     QTreeWidgetItem* curItem = ui->treePass->currentItem();
0200     if (!curItem)
0201         return;
0202 
0203     PasswordEntry entry = curItem->data(0, Qt::UserRole + 10).value<PasswordEntry>();
0204     QApplication::clipboard()->setText(entry.password);
0205 }
0206 
0207 void AutoFillManager::copyUsername()
0208 {
0209     QTreeWidgetItem* curItem = ui->treePass->currentItem();
0210     if (!curItem)
0211         return;
0212 
0213     PasswordEntry entry = curItem->data(0, Qt::UserRole + 10).value<PasswordEntry>();
0214     QApplication::clipboard()->setText(entry.username);
0215 }
0216 
0217 void AutoFillManager::removePass()
0218 {
0219     QTreeWidgetItem* curItem = ui->treePass->currentItem();
0220     if (!curItem) {
0221         return;
0222     }
0223 
0224     PasswordEntry entry = curItem->data(0, Qt::UserRole + 10).value<PasswordEntry>();
0225     mApp->autoFill()->removeEntry(entry);
0226 
0227     delete curItem;
0228 }
0229 
0230 void AutoFillManager::removeAllPass()
0231 {
0232     QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Confirmation"),
0233                                          tr("Are you sure you want to delete all passwords on your computer?"), QMessageBox::Yes | QMessageBox::No);
0234     if (button != QMessageBox::Yes) {
0235         return;
0236     }
0237 
0238     mApp->autoFill()->removeAllEntries();
0239     ui->treePass->clear();
0240 }
0241 
0242 void AutoFillManager::editPass()
0243 {
0244     QTreeWidgetItem* curItem = ui->treePass->currentItem();
0245     if (!curItem) {
0246         return;
0247     }
0248 
0249     PasswordEntry entry = curItem->data(0, Qt::UserRole + 10).value<PasswordEntry>();
0250 
0251     bool ok;
0252     QString text = QInputDialog::getText(this, tr("Edit password"), tr("Change password:"), QLineEdit::Normal, entry.password, &ok);
0253 
0254     if (ok && !text.isEmpty() && text != entry.password) {
0255         QByteArray oldPass = "=" + PasswordManager::urlEncodePassword(entry.password);
0256         QByteArray newPass = "=" + PasswordManager::urlEncodePassword(text);
0257         entry.data.replace(oldPass, newPass);
0258         entry.password = text;
0259 
0260         if (mApp->autoFill()->updateEntry(entry)) {
0261             QVariant v;
0262             v.setValue(entry);
0263             curItem->setData(0, Qt::UserRole + 10, v);
0264 
0265             if (m_passwordsShown) {
0266                 curItem->setText(2, text);
0267             }
0268         }
0269     }
0270 }
0271 
0272 void AutoFillManager::removeExcept()
0273 {
0274     QTreeWidgetItem* curItem = ui->treeExcept->currentItem();
0275     if (!curItem) {
0276         return;
0277     }
0278     QString id = curItem->data(0, Qt::UserRole + 10).toString();
0279     QSqlQuery query(SqlDatabase::instance()->database());
0280     query.prepare(QSL("DELETE FROM autofill_exceptions WHERE id=?"));
0281     query.addBindValue(id);
0282     query.exec();
0283 
0284     delete curItem;
0285 }
0286 
0287 void AutoFillManager::removeAllExcept()
0288 {
0289     QSqlQuery query(SqlDatabase::instance()->database());
0290     query.exec(QSL("DELETE FROM autofill_exceptions"));
0291 
0292     ui->treeExcept->clear();
0293 }
0294 
0295 void AutoFillManager::showExceptions()
0296 {
0297     ui->tabWidget->setCurrentIndex(1);
0298 }
0299 
0300 void AutoFillManager::importPasswords()
0301 {
0302     m_fileName = QzTools::getOpenFileName(QSL("AutoFill-ImportPasswords"), this, tr("Choose file..."), QDir::homePath() + QSL("/passwords.xml"), QSL("*.xml"));
0303 
0304     if (m_fileName.isEmpty()) {
0305         return;
0306     }
0307 
0308     QTimer::singleShot(0, this, &AutoFillManager::slotImportPasswords);
0309 }
0310 
0311 void AutoFillManager::exportPasswords()
0312 {
0313     m_fileName = QzTools::getSaveFileName(QSL("AutoFill-ExportPasswords"), this, tr("Choose file..."), QDir::homePath() + QSL("/passwords.xml"), QSL("*.xml"));
0314 
0315     if (m_fileName.isEmpty()) {
0316         return;
0317     }
0318 
0319     QTimer::singleShot(0, this, &AutoFillManager::slotExportPasswords);
0320 }
0321 
0322 void AutoFillManager::slotImportPasswords()
0323 {
0324     QFile file(m_fileName);
0325 
0326     if (!file.open(QFile::ReadOnly)) {
0327         ui->importExportLabel->setText(tr("Cannot read file!"));
0328         return;
0329     }
0330 
0331     QApplication::setOverrideCursor(Qt::WaitCursor);
0332 
0333     bool status = mApp->autoFill()->importPasswords(file.readAll());
0334     file.close();
0335 
0336     ui->importExportLabel->setText(status ? tr("Successfully imported") : tr("Error while importing!"));
0337     loadPasswords();
0338 
0339     QApplication::restoreOverrideCursor();
0340 }
0341 
0342 void AutoFillManager::slotExportPasswords()
0343 {
0344     QFile file(m_fileName);
0345 
0346     if (!file.open(QFile::WriteOnly)) {
0347         ui->importExportLabel->setText(tr("Cannot write to file!"));
0348         return;
0349     }
0350 
0351     QApplication::setOverrideCursor(Qt::WaitCursor);
0352 
0353     file.write(mApp->autoFill()->exportPasswords());
0354     file.close();
0355 
0356     ui->importExportLabel->setText(tr("Successfully exported"));
0357 
0358     QApplication::restoreOverrideCursor();
0359 }
0360 
0361 void AutoFillManager::currentPasswordBackendChanged()
0362 {
0363     ui->currentBackend->setText(QSL("<b>%1</b>").arg(m_passwordManager->activeBackend()->name()));
0364     ui->backendOptions->setVisible(m_passwordManager->activeBackend()->hasSettings());
0365 
0366     QTimer::singleShot(0, this, &AutoFillManager::loadPasswords);
0367 }
0368 
0369 void AutoFillManager::passwordContextMenu(const QPoint &pos)
0370 {
0371     auto *menu = new QMenu;
0372     menu->setAttribute(Qt::WA_DeleteOnClose);
0373     menu->addAction(tr("Copy Username"), this, &AutoFillManager::copyUsername);
0374     menu->addAction(tr("Copy Password"), this, &AutoFillManager::copyPassword);
0375     menu->addSeparator();
0376     menu->addAction(tr("Edit Password"), this, &AutoFillManager::editPass);
0377     menu->popup(ui->treePass->viewport()->mapToGlobal(pos));
0378 }
0379 
0380 AutoFillManager::~AutoFillManager()
0381 {
0382     delete ui;
0383 }