diff --git a/templateparser/src/customtemplates.cpp b/templateparser/src/customtemplates.cpp index eebb94c2..0a4b847c 100644 --- a/templateparser/src/customtemplates.cpp +++ b/templateparser/src/customtemplates.cpp @@ -1,587 +1,587 @@ /* * Copyright (C) 2006 Dmitry Morozhnikov * Copyright (C) 2011-2017 Laurent Montel * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "customtemplates.h" #include "customtemplates_kfg.h" #include "globalsettings_templateparser.h" #include "ui_customtemplates_base.h" #include "kpimtextedit/plaintexteditor.h" #include "templateparseremailaddressrequesterinterfacewidget.h" #include #include #include #include #include using namespace TemplateParser; CustomTemplates::CustomTemplates(const QList &actionCollection, QWidget *parent) : QWidget(parent), mBlockChangeSignal(false) { mUi = new Ui_CustomTemplatesBase; mUi->setupUi(this); mUi->mAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); mUi->mAdd->setEnabled(false); mUi->mRemove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); mUi->mDuplicate->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); mUi->mList->setColumnWidth(0, 100); mUi->mList->header()->setStretchLastSection(true); mUi->mList->setItemDelegate(new CustomTemplateItemDelegate(this)); mUi->mList->header()->setMovable(false); mUi->mEditFrame->setEnabled(false); mUi->mName->setTrapReturnKey(true); connect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged, this, &CustomTemplates::slotTextChanged); connect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged); connect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged); connect(mUi->mName, &KLineEdit::textChanged, this, &CustomTemplates::slotNameChanged); connect(mUi->mName, &KLineEdit::returnPressed, this, &CustomTemplates::slotAddClicked); connect(mUi->mInsertCommand, SIGNAL(insertCommand(QString,int)), this, SLOT(slotInsertCommand(QString,int))); connect(mUi->mAdd, &QPushButton::clicked, this, &CustomTemplates::slotAddClicked); connect(mUi->mRemove, &QPushButton::clicked, this, &CustomTemplates::slotRemoveClicked); connect(mUi->mDuplicate, &QPushButton::clicked, this, &CustomTemplates::slotDuplicateClicked); connect(mUi->mList, &QTreeWidget::currentItemChanged, this, &CustomTemplates::slotListSelectionChanged); connect(mUi->mList, &QTreeWidget::itemChanged, this, &CustomTemplates::slotItemChanged); - connect(mUi->mType, static_cast(&KComboBox::activated), this, &CustomTemplates::slotTypeActivated); + connect(mUi->mType, QOverload::of(&KComboBox::activated), this, &CustomTemplates::slotTypeActivated); connect(mUi->mKeySequenceWidget, &KKeySequenceWidget::keySequenceChanged, this, &CustomTemplates::slotShortcutChanged); mUi->mKeySequenceWidget->setCheckActionCollections(actionCollection); mReplyPix = QIcon::fromTheme(QStringLiteral("mail-reply-sender")); mReplyAllPix = QIcon::fromTheme(QStringLiteral("mail-reply-all")); mForwardPix = QIcon::fromTheme(QStringLiteral("mail-forward")); mUi->mType->clear(); mUi->mType->addItem(QPixmap(), i18nc("Message->", "Universal")); mUi->mType->addItem(mReplyPix, i18nc("Message->", "Reply")); mUi->mType->addItem(mReplyAllPix, i18nc("Message->", "Reply to All")); mUi->mType->addItem(mForwardPix, i18nc("Message->", "Forward")); mUi->mHelp->setText(i18n("How does this work?")); connect(mUi->mHelp, &QLabel::linkActivated, this, &CustomTemplates::slotHelpLinkClicked); mUi->mHelp->setContextMenuPolicy(Qt::NoContextMenu); slotNameChanged(mUi->mName->text()); } void CustomTemplates::slotHelpLinkClicked(const QString &) { const QString help = i18n("" "

Here you can add, edit, and delete custom message " "templates to use when you compose a reply or forwarding message. " "Create the custom template by typing the name into the input box " "and press the '+' button. Also, you can bind a keyboard " "combination to the template for faster operations.

" "

Message templates support substitution commands, " "by simply typing them or selecting them from the " "Insert command menu.

" "

There are four types of custom templates: used to " "Reply, Reply to All, Forward, and " "Universal which can be used for all kinds of operations. " "You cannot bind a keyboard shortcut to Universal templates.

" "
"); QWhatsThis::showText(QCursor::pos(), help); } CustomTemplates::~CustomTemplates() { disconnect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged, this, &CustomTemplates::slotTextChanged); disconnect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged); disconnect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged); delete mUi; mUi = nullptr; } void CustomTemplates::slotNameChanged(const QString &text) { mUi->mAdd->setEnabled(!text.trimmed().isEmpty()); } QString CustomTemplates::indexToType(int index) { QString typeStr; switch (index) { case TUniversal: typeStr = i18nc("Message->", "Universal"); break; /* case TNewMessage: typeStr = i18n( "New Message" ); break; */ case TReply: typeStr = i18nc("Message->", "Reply"); break; case TReplyAll: typeStr = i18nc("Message->", "Reply to All"); break; case TForward: typeStr = i18nc("Message->", "Forward"); break; default: typeStr = i18nc("Message->", "Unknown"); break; } return typeStr; } void CustomTemplates::slotTextChanged() { QTreeWidgetItem *item = mUi->mList->currentItem(); if (item) { CustomTemplateItem *vitem = static_cast(item); vitem->setContent(mUi->mEdit->toPlainText()); if (!mBlockChangeSignal) { vitem->setTo(mUi->mToEdit->text()); vitem->setCc(mUi->mCCEdit->text()); } } if (!mBlockChangeSignal) { Q_EMIT changed(); } } void CustomTemplates::iconFromType(CustomTemplates::Type type, CustomTemplateItem *item) { switch (type) { case TReply: item->setIcon(0, mReplyPix); break; case TReplyAll: item->setIcon(0, mReplyAllPix); break; case TForward: item->setIcon(0, mForwardPix); break; default: item->setIcon(0, QPixmap()); break; }; } void CustomTemplates::load() { const QStringList list = TemplateParserSettings::self()->customTemplates(); mUi->mList->clear(); QStringList::const_iterator end(list.constEnd()); for (QStringList::const_iterator it = list.constBegin(); it != end; ++it) { CTemplates t(*it); QKeySequence shortcut(t.shortcut()); CustomTemplates::Type type = static_cast(t.type()); CustomTemplateItem *item = new CustomTemplateItem(mUi->mList, *it, t.content(), shortcut, type, t.to(), t.cC()); item->setText(1, *it); item->setText(0, indexToType(type)); iconFromType(type, item); } const bool enabled = mUi->mList->topLevelItemCount() > 0 && mUi->mList->currentItem(); mUi->mRemove->setEnabled(enabled); mUi->mDuplicate->setEnabled(enabled); } void CustomTemplates::save() { // Before saving the new templates, delete the old ones. That needs to be done before // saving, since otherwise a new template with the new name wouldn't get saved. KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("customtemplatesrc"), KConfig::NoGlobals); for (const QString &item : qAsConst(mItemsToDelete)) { CTemplates t(item); const QString configGroup = t.currentGroup(); config->deleteGroup(configGroup); } QStringList list; QTreeWidgetItemIterator lit(mUi->mList); while (*lit) { CustomTemplateItem *it = static_cast(*lit); const QString name = it->text(1); list.append(name); CTemplates t(name); QString content = it->content(); if (content.trimmed().isEmpty()) { content = QStringLiteral("%BLANK"); } t.setContent(content); t.setShortcut(it->shortcut().toString()); t.setType(it->customType()); t.setTo(it->to()); t.setCC(it->cc()); t.save(); ++lit; } TemplateParserSettings::self()->setCustomTemplates(list); TemplateParserSettings::self()->save(); Q_EMIT templatesUpdated(); } void CustomTemplates::slotInsertCommand(const QString &cmd, int adjustCursor) { QTextCursor cursor = mUi->mEdit->editor()->textCursor(); cursor.insertText(cmd); cursor.setPosition(cursor.position() + adjustCursor); mUi->mEdit->editor()->setTextCursor(cursor); mUi->mEdit->editor()->setFocus(); } bool CustomTemplates::nameAlreadyExists(const QString &str, QTreeWidgetItem *item) { QTreeWidgetItemIterator lit(mUi->mList); while (*lit) { const QString name = (*lit)->text(1); if ((name == str) && ((*lit) != item)) { KMessageBox::error( this, i18n("A template with same name already exists."), i18n("Cannot create template")); return true; } ++lit; } return false; } void CustomTemplates::slotAddClicked() { const QString str = mUi->mName->text(); if (!str.isEmpty()) { if (nameAlreadyExists(str)) { return; } QKeySequence nullShortcut; CustomTemplateItem *item = new CustomTemplateItem(mUi->mList, str, QString(), nullShortcut, TUniversal, QString(), QString()); item->setText(0, indexToType(TUniversal)); item->setText(1, str); mUi->mList->setCurrentItem(item); mUi->mRemove->setEnabled(true); mUi->mDuplicate->setEnabled(true); mUi->mName->clear(); mUi->mKeySequenceWidget->setEnabled(false); if (!mBlockChangeSignal) { Q_EMIT changed(); } } } QString CustomTemplates::createUniqueName(const QString &name) const { QString uniqueName = name; int counter = 0; bool found = true; while (found) { found = false; QTreeWidgetItemIterator lit(mUi->mList); while (*lit) { const QString itemName = (*lit)->text(1); if (!itemName.compare(uniqueName)) { found = true; ++counter; uniqueName = name; uniqueName += QLatin1String(" (") + QString::number(counter) + QLatin1String(")"); break; } lit++; } } return uniqueName; } void CustomTemplates::slotDuplicateClicked() { QTreeWidgetItem *currentItem = mUi->mList->currentItem(); if (!currentItem) { return; } CustomTemplateItem *origItem = static_cast(currentItem); const QString templateName = createUniqueName(origItem->text(1)); QKeySequence nullShortcut; CustomTemplates::Type type = origItem->customType(); CustomTemplateItem *item = new CustomTemplateItem(mUi->mList, templateName, origItem->content(), nullShortcut, type, origItem->to(), origItem->cc()); item->setText(0, indexToType(type)); item->setText(1, templateName); iconFromType(type, item); mUi->mList->setCurrentItem(item); mUi->mRemove->setEnabled(true); mUi->mDuplicate->setEnabled(true); mUi->mName->clear(); mUi->mKeySequenceWidget->setEnabled(type != TUniversal); Q_EMIT changed(); } void CustomTemplates::slotRemoveClicked() { QTreeWidgetItem *item = mUi->mList->currentItem(); if (!item) { return; } const QString templateName = item->text(1); if (KMessageBox::warningContinueCancel( this, i18nc("@info", "Do you really want to remove template \"%1\"?", templateName), i18nc("@title:window", "Remove Template?"), KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Continue) { mItemsToDelete.append(templateName); delete mUi->mList->takeTopLevelItem(mUi->mList->indexOfTopLevelItem(item)); mUi->mRemove->setEnabled(mUi->mList->topLevelItemCount() > 0); mUi->mDuplicate->setEnabled(mUi->mList->topLevelItemCount() > 0); if (!mBlockChangeSignal) { Q_EMIT changed(); } } } void CustomTemplates::slotListSelectionChanged() { QTreeWidgetItem *item = mUi->mList->currentItem(); if (item) { mUi->mEditFrame->setEnabled(true); mUi->mRemove->setEnabled(true); mUi->mDuplicate->setEnabled(true); CustomTemplateItem *vitem = static_cast(item); mBlockChangeSignal = true; mUi->mEdit->setPlainText(vitem->content()); mUi->mKeySequenceWidget->setKeySequence(vitem->shortcut(), KKeySequenceWidget::NoValidate); CustomTemplates::Type type = vitem->customType(); mUi->mType->setCurrentIndex(mUi->mType->findText(indexToType(type))); mUi->mToEdit->setText(vitem->to()); mUi->mCCEdit->setText(vitem->cc()); mBlockChangeSignal = false; // I think the logic (originally 'vitem->mType==TUniversal') was inverted here: // a key shortcut is only allowed for a specific type of template and not for // a universal, as otherwise we won't know what sort of action to do when the // key sequence is activated! // This agrees with KMMainWidget::updateCustomTemplateMenus() -- marten mUi->mKeySequenceWidget->setEnabled(type != TUniversal); } else { mUi->mEditFrame->setEnabled(false); mUi->mEdit->editor()->clear(); // see above mUi->mKeySequenceWidget->clearKeySequence(); mUi->mType->setCurrentIndex(0); mUi->mToEdit->clear(); mUi->mCCEdit->clear(); } } void CustomTemplates::slotTypeActivated(int index) { QTreeWidgetItem *item = mUi->mList->currentItem(); if (item) { CustomTemplateItem *vitem = static_cast(item); CustomTemplates::Type customtype = static_cast(index); vitem->setCustomType(customtype); vitem->setText(0, indexToType(customtype)); iconFromType(customtype, vitem); // see slotListSelectionChanged() above mUi->mKeySequenceWidget->setEnabled(customtype != TUniversal); if (!mBlockChangeSignal) { Q_EMIT changed(); } } } void CustomTemplates::slotShortcutChanged(const QKeySequence &newSeq) { QTreeWidgetItem *item = mUi->mList->currentItem(); if (item) { CustomTemplateItem *vitem = static_cast(item); vitem->setShortcut(newSeq); mUi->mKeySequenceWidget->applyStealShortcut(); } if (!mBlockChangeSignal) { Q_EMIT changed(); } } void CustomTemplates::slotItemChanged(QTreeWidgetItem *item, int column) { if (item) { CustomTemplateItem *vitem = static_cast(item); if (column == 1) { const QString newName = vitem->text(1); if (!newName.isEmpty()) { const QString oldName = vitem->oldName(); if (nameAlreadyExists(newName, item)) { vitem->setText(1, oldName); return; } if (newName != oldName) { mItemsToDelete.append(oldName); vitem->setOldName(newName); if (!mBlockChangeSignal) { Q_EMIT changed(); } } } } } } CustomTemplateItemDelegate::CustomTemplateItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } CustomTemplateItemDelegate::~CustomTemplateItemDelegate() { } void CustomTemplateItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { KLineEdit *lineEdit = static_cast(editor); const QString text = lineEdit->text(); if (!text.isEmpty()) { model->setData(index, text, Qt::EditRole); } } QWidget *CustomTemplateItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() == 1) { return QStyledItemDelegate::createEditor(parent, option, index); } return nullptr; } CustomTemplateItem::CustomTemplateItem(QTreeWidget *parent, const QString &name, const QString &content, const QKeySequence &shortcut, CustomTemplates::Type type, const QString &to, const QString &cc) : QTreeWidgetItem(parent), mName(name), mContent(content), mShortcut(shortcut), mType(type), mTo(to), mCC(cc) { setFlags(flags() | Qt::ItemIsEditable); } CustomTemplateItem::~CustomTemplateItem() { } void CustomTemplateItem::setCustomType(CustomTemplates::Type type) { mType = type; } CustomTemplates::Type CustomTemplateItem::customType() const { return mType; } QString CustomTemplateItem::to() const { return mTo; } QString CustomTemplateItem::cc() const { return mCC; } QString CustomTemplateItem::content() const { return mContent; } void CustomTemplateItem::setContent(const QString &content) { mContent = content; } void CustomTemplateItem::setTo(const QString &to) { mTo = to; } void CustomTemplateItem::setCc(const QString &cc) { mCC = cc; } QKeySequence CustomTemplateItem::shortcut() const { return mShortcut; } void CustomTemplateItem::setShortcut(const QKeySequence &shortcut) { mShortcut = shortcut; } QString CustomTemplateItem::oldName() const { return mName; } void CustomTemplateItem::setOldName(const QString &name) { mName = name; } diff --git a/templateparser/src/customtemplatesmenu.cpp b/templateparser/src/customtemplatesmenu.cpp index 101b8365..a93cce09 100644 --- a/templateparser/src/customtemplatesmenu.cpp +++ b/templateparser/src/customtemplatesmenu.cpp @@ -1,259 +1,256 @@ /* * Copyright (C) 2006 Dmitry Morozhnikov * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "customtemplatesmenu.h" #include "customtemplates.h" #include "customtemplates_kfg.h" #include "globalsettings_templateparser.h" #include #include #include #include #include #include using namespace TemplateParser; class TemplateParser::CustomTemplatesMenuPrivate { public: CustomTemplatesMenuPrivate() : mOwnerActionCollection(nullptr), mCustomReplyActionMenu(nullptr), mCustomReplyAllActionMenu(nullptr), mCustomForwardActionMenu(nullptr), mCustomReplyMapper(nullptr), mCustomReplyAllMapper(nullptr), mCustomForwardMapper(nullptr) { } ~CustomTemplatesMenuPrivate() { delete mCustomReplyActionMenu; delete mCustomReplyAllActionMenu; delete mCustomForwardActionMenu; delete mCustomReplyMapper; delete mCustomReplyAllMapper; delete mCustomForwardMapper; } KActionCollection *mOwnerActionCollection; QStringList mCustomTemplates; QList mCustomTemplateActions; // Custom template actions menu KActionMenu *mCustomReplyActionMenu; KActionMenu *mCustomReplyAllActionMenu; KActionMenu *mCustomForwardActionMenu; // Signal mappers for custom template actions QSignalMapper *mCustomReplyMapper; QSignalMapper *mCustomReplyAllMapper; QSignalMapper *mCustomForwardMapper; }; CustomTemplatesMenu::CustomTemplatesMenu(QWidget *owner, KActionCollection *ac) : d(new TemplateParser::CustomTemplatesMenuPrivate) { d->mOwnerActionCollection = ac; d->mCustomForwardActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward-custom")), i18n("With Custom Template"), owner); d->mOwnerActionCollection->addAction(QStringLiteral("custom_forward"), d->mCustomForwardActionMenu); d->mCustomReplyActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-custom")), i18n("Reply With Custom Template"), owner); d->mOwnerActionCollection->addAction(QStringLiteral("custom_reply"), d->mCustomReplyActionMenu); d->mCustomReplyAllActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-all-custom")), i18n("Reply to All With Custom Template"), owner); d->mOwnerActionCollection->addAction(QStringLiteral("custom_reply_all"), d->mCustomReplyAllActionMenu); d->mCustomForwardMapper = new QSignalMapper(this); - connect(d->mCustomForwardMapper, static_cast(&QSignalMapper::mapped), - this, &CustomTemplatesMenu::slotForwardSelected); + connect(d->mCustomForwardMapper, QOverload::of(&QSignalMapper::mapped), this, &CustomTemplatesMenu::slotForwardSelected); d->mCustomReplyMapper = new QSignalMapper(this); - connect(d->mCustomReplyMapper, static_cast(&QSignalMapper::mapped), - this, &CustomTemplatesMenu::slotReplySelected); + connect(d->mCustomReplyMapper, QOverload::of(&QSignalMapper::mapped), this, &CustomTemplatesMenu::slotReplySelected); d->mCustomReplyAllMapper = new QSignalMapper(this); - connect(d->mCustomReplyAllMapper, static_cast(&QSignalMapper::mapped), - this, &CustomTemplatesMenu::slotReplyAllSelected); + connect(d->mCustomReplyAllMapper, QOverload::of(&QSignalMapper::mapped), this, &CustomTemplatesMenu::slotReplyAllSelected); update(); } CustomTemplatesMenu::~CustomTemplatesMenu() { clear(); delete d; } KActionMenu *CustomTemplatesMenu::replyActionMenu() const { return d->mCustomReplyActionMenu; } KActionMenu *CustomTemplatesMenu::replyAllActionMenu() const { return d->mCustomReplyAllActionMenu; } KActionMenu *CustomTemplatesMenu::forwardActionMenu() const { return d->mCustomForwardActionMenu; } void CustomTemplatesMenu::clear() { QListIterator ait(d->mCustomTemplateActions); while (ait.hasNext()) { QAction *action = ait.next(); d->mCustomReplyMapper->removeMappings(action); d->mCustomReplyAllMapper->removeMappings(action); d->mCustomForwardMapper->removeMappings(action); } qDeleteAll(d->mCustomTemplateActions); d->mCustomTemplateActions.clear(); d->mCustomReplyActionMenu->menu()->clear(); d->mCustomReplyAllActionMenu->menu()->clear(); d->mCustomForwardActionMenu->menu()->clear(); d->mCustomTemplates.clear(); } void CustomTemplatesMenu::update() { clear(); const QStringList list = TemplateParserSettings::self()->customTemplates(); QStringList::const_iterator it = list.constBegin(); QStringList::const_iterator end = list.constEnd(); int idx = 0; int replyc = 0; int replyallc = 0; int forwardc = 0; for (; it != end; ++it) { CTemplates t(*it); d->mCustomTemplates.append(*it); QString nameAction(*it); nameAction.replace(QLatin1Char('&'), QStringLiteral("&&")); const QString nameActionName = nameAction.replace(QLatin1Char(' '), QLatin1Char('_')); QAction *action; switch (t.type()) { case CustomTemplates::TReply: action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut()); d->mOwnerActionCollection->addAction(nameActionName, action); connect(action, SIGNAL(triggered(bool)), d->mCustomReplyMapper, SLOT(map())); d->mCustomReplyMapper->setMapping(action, idx); d->mCustomReplyActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++replyc; break; case CustomTemplates::TReplyAll: action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut()); d->mOwnerActionCollection->addAction(nameActionName, action); connect(action, SIGNAL(triggered(bool)), d->mCustomReplyAllMapper, SLOT(map())); d->mCustomReplyAllMapper->setMapping(action, idx); d->mCustomReplyAllActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++replyallc; break; case CustomTemplates::TForward: action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis d->mOwnerActionCollection->addAction(nameActionName, action); d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut()); connect(action, SIGNAL(triggered(bool)), d->mCustomForwardMapper, SLOT(map())); d->mCustomForwardMapper->setMapping(action, idx); d->mCustomForwardActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++forwardc; break; case CustomTemplates::TUniversal: action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis d->mOwnerActionCollection->addAction(nameActionName, action); connect(action, SIGNAL(triggered(bool)), d->mCustomReplyMapper, SLOT(map())); d->mCustomReplyMapper->setMapping(action, idx); d->mCustomReplyActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++replyc; action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis connect(action, SIGNAL(triggered(bool)), d->mCustomReplyAllMapper, SLOT(map())); d->mCustomReplyAllMapper->setMapping(action, idx); d->mCustomReplyAllActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++replyallc; action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis connect(action, SIGNAL(triggered(bool)), d->mCustomForwardMapper, SLOT(map())); d->mCustomForwardMapper->setMapping(action, idx); d->mCustomForwardActionMenu->addAction(action); d->mCustomTemplateActions.append(action); ++forwardc; break; } ++idx; } if (!replyc) { QAction *noAction = d->mCustomReplyActionMenu->menu()->addAction(i18n("(no custom templates)")); noAction->setEnabled(false); d->mCustomReplyActionMenu->setEnabled(false); } if (!replyallc) { QAction *noAction = d->mCustomReplyAllActionMenu->menu()->addAction(i18n("(no custom templates)")); noAction->setEnabled(false); d->mCustomReplyAllActionMenu->setEnabled(false); } if (!forwardc) { QAction *noAction = d->mCustomForwardActionMenu->menu()->addAction(i18n("(no custom templates)")); noAction->setEnabled(false); d->mCustomForwardActionMenu->setEnabled(false); } } void CustomTemplatesMenu::slotReplySelected(int idx) { Q_EMIT replyTemplateSelected(d->mCustomTemplates.at(idx)); } void CustomTemplatesMenu::slotReplyAllSelected(int idx) { Q_EMIT replyAllTemplateSelected(d->mCustomTemplates.at(idx)); } void CustomTemplatesMenu::slotForwardSelected(int idx) { Q_EMIT forwardTemplateSelected(d->mCustomTemplates.at(idx)); } diff --git a/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp b/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp index 0b3df3b4..68194e58 100644 --- a/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp +++ b/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp @@ -1,181 +1,181 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "checkphishingurljob.h" #include "checkphishingurlutil.h" #include #include #include #include #include using namespace WebEngineViewer; WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson = true; class WebEngineViewer::CheckPhishingUrlJobPrivate { public: CheckPhishingUrlJobPrivate() : mNetworkAccessManager(nullptr) { } QUrl mUrl; QNetworkAccessManager *mNetworkAccessManager; }; CheckPhishingUrlJob::CheckPhishingUrlJob(QObject *parent) : QObject(parent), d(new WebEngineViewer::CheckPhishingUrlJobPrivate) { d->mNetworkAccessManager = new QNetworkAccessManager(this); connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &CheckPhishingUrlJob::slotCheckUrlFinished); connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &CheckPhishingUrlJob::slotSslErrors); } CheckPhishingUrlJob::~CheckPhishingUrlJob() { delete d; } void CheckPhishingUrlJob::slotSslErrors(QNetworkReply *reply, const QList &error) { qCDebug(WEBENGINEVIEWER_LOG) << " void CheckPhishingUrlJob::slotSslErrors(QNetworkReply *reply, const QList &error)" << error.count(); reply->ignoreSslErrors(error); } void CheckPhishingUrlJob::parse(const QByteArray &replyStr) { QJsonDocument document = QJsonDocument::fromJson(replyStr); if (document.isNull()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl); } else { const QVariantMap answer = document.toVariant().toMap(); if (answer.isEmpty()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl); return; } else { const QVariantList info = answer.value(QStringLiteral("matches")).toList(); if (info.count() == 1) { const QVariantMap map = info.at(0).toMap(); const QString threatTypeStr = map[QStringLiteral("threatType")].toString(); const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString(); uint verifyCacheAfterThisTime = 0; if (!cacheDuration.isEmpty()) { double cacheDurationValue = WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(cacheDuration); if (cacheDurationValue > 0) { verifyCacheAfterThisTime = WebEngineViewer::CheckPhishingUrlUtil::refreshingCacheAfterThisTime(cacheDurationValue); } } if (threatTypeStr == QStringLiteral("MALWARE")) { const QVariantMap urlMap = map[QStringLiteral("threat")].toMap(); if (urlMap.count() == 1) { if (urlMap[QStringLiteral("url")].toString() == d->mUrl.toString()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl, verifyCacheAfterThisTime); return; } } } else { qWarning() << " CheckPhishingUrlJob::parse threatTypeStr : " << threatTypeStr; } } Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl); } } } void CheckPhishingUrlJob::slotCheckUrlFinished(QNetworkReply *reply) { parse(reply->readAll()); reply->deleteLater(); deleteLater(); } void CheckPhishingUrlJob::setUrl(const QUrl &url) { d->mUrl = url; } QByteArray CheckPhishingUrlJob::jsonRequest() const { QVariantMap clientMap; QVariantMap map; clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE")); clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps()); map.insert(QStringLiteral("client"), clientMap); QVariantMap threatMap; const QVariantList platformList = { QStringLiteral("WINDOWS") }; threatMap.insert(QStringLiteral("platformTypes"), platformList); const QVariantList threatTypesList = { QStringLiteral("MALWARE") }; threatMap.insert(QStringLiteral("threatTypes"), threatTypesList); const QVariantList threatEntryTypesList = { QStringLiteral("URL") }; threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList); QVariantList threatEntriesList; QVariantMap urlMap; urlMap.insert(QStringLiteral("url"), d->mUrl.toString()); threatEntriesList.append(urlMap); threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList); map.insert(QStringLiteral("threatInfo"), threatMap); const QJsonDocument postData = QJsonDocument::fromVariant(map); const QByteArray baPostData = postData.toJson(webengineview_useCompactJson ? QJsonDocument::Compact : QJsonDocument::Indented); return baPostData; } void CheckPhishingUrlJob::start() { if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl); deleteLater(); } else if (canStart()) { QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/threatMatches:find")); safeUrl.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey()); QNetworkRequest request(safeUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); const QByteArray baPostData = jsonRequest(); qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData; Q_EMIT debugJson(baPostData); //curl -H "Content-Type: application/json" -X POST -d '{"client":{"clientId":"KDE","clientVersion":"5.4.0"},"threatInfo":{"platformTypes":["WINDOWS"],"threatEntries":[{"url":"http://www.kde.org"}],"threatEntryTypes":["URL"],"threatTypes":["MALWARE"]}}' https://safebrowsing.googleapis.com/v4/threatMatches:find?key=AIzaSyBS62pXATjabbH2RM_jO2EzDg1mTMHlnyo QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData); - connect(reply, static_cast(&QNetworkReply::error), this, &CheckPhishingUrlJob::slotError); + connect(reply, QOverload::of(&QNetworkReply::error), this, &CheckPhishingUrlJob::slotError); } else { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl); deleteLater(); } } void CheckPhishingUrlJob::slotError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString(); reply->deleteLater(); deleteLater(); } bool CheckPhishingUrlJob::canStart() const { return d->mUrl.isValid(); } diff --git a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp index 44b3725c..f3ed2d47 100644 --- a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp +++ b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp @@ -1,409 +1,409 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "createphishingurldatabasejob.h" #include "checkphishingurlutil.h" #include "updatedatabaseinfo.h" #include "webengineviewer_debug.h" #include #include #include using namespace WebEngineViewer; WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_CreatePhishingUrlDataBaseJob = true; class WebEngineViewer::CreatePhishingUrlDataBaseJobPrivate { public: CreatePhishingUrlDataBaseJobPrivate() : mContraintsCompressionType(CreatePhishingUrlDataBaseJob::RawCompression), mDataBaseDownloadNeeded(CreatePhishingUrlDataBaseJob::FullDataBase), mNetworkAccessManager(nullptr) { } UpdateDataBaseInfo::CompressionType parseCompressionType(const QString &str); RiceDeltaEncoding parseRiceDeltaEncoding(const QMap &map); QVector parseRemovals(const QVariantList &lst); QVector parseAdditions(const QVariantList &lst); QString mDataBaseState; CreatePhishingUrlDataBaseJob::ContraintsCompressionType mContraintsCompressionType; CreatePhishingUrlDataBaseJob::DataBaseDownloadType mDataBaseDownloadNeeded; QNetworkAccessManager *mNetworkAccessManager; }; CreatePhishingUrlDataBaseJob::CreatePhishingUrlDataBaseJob(QObject *parent) : QObject(parent), d(new CreatePhishingUrlDataBaseJobPrivate) { d->mNetworkAccessManager = new QNetworkAccessManager(this); connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &CreatePhishingUrlDataBaseJob::slotDownloadDataBaseFinished); connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &CreatePhishingUrlDataBaseJob::slotSslErrors); } CreatePhishingUrlDataBaseJob::~CreatePhishingUrlDataBaseJob() { delete d; } void CreatePhishingUrlDataBaseJob::slotSslErrors(QNetworkReply *reply, const QList &error) { qCDebug(WEBENGINEVIEWER_LOG) << " void CreatePhishingUrlDataBaseJob::slotSslErrors(QNetworkReply *reply, const QList &error)" << error.count(); reply->ignoreSslErrors(error); } void CreatePhishingUrlDataBaseJob::start() { if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) { Q_EMIT finished(UpdateDataBaseInfo(), BrokenNetwork); deleteLater(); } else { QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch")); safeUrl.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey()); //qCDebug(WEBENGINEVIEWER_LOG) << " safeUrl" << safeUrl; QNetworkRequest request(safeUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); const QByteArray baPostData = jsonRequest(); Q_EMIT debugJson(baPostData); qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData; //curl -H "Content-Type: application/json" -X POST -d '{"client":{"clientId":"KDE","clientVersion":"5.4.0"},"threatInfo":{"platformTypes":["WINDOWS"],"threatEntries":[{"url":"http://www.kde.org"}],"threatEntryTypes":["URL"],"threatTypes":["MALWARE"]}}' https://safebrowsing.googleapis.com/v4/threatMatches:find?key=AIzaSyBS62pXATjabbH2RM_jO2EzDg1mTMHlnyo QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData); - connect(reply, static_cast(&QNetworkReply::error), this, &CreatePhishingUrlDataBaseJob::slotError); + connect(reply, QOverload::of(&QNetworkReply::error), this, &CreatePhishingUrlDataBaseJob::slotError); } } void CreatePhishingUrlDataBaseJob::setDataBaseState(const QString &value) { d->mDataBaseState = value; } void CreatePhishingUrlDataBaseJob::slotError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qWarning() << " error " << error << " error string : " << reply->errorString(); reply->deleteLater(); deleteLater(); } QByteArray CreatePhishingUrlDataBaseJob::jsonRequest() const { #if 0 { "client": { "clientId": "yourcompanyname", "clientVersion": "1.5.2" }, "listUpdateRequests": [{ "threatType": "MALWARE", "platformType": "WINDOWS", "threatEntryType": "URL", "state": "Gg4IBBADIgYQgBAiAQEoAQ==", "constraints": { "maxUpdateEntries": 2048, "maxDatabaseEntries": 4096, "region": "US", "supportedCompressions": ["RAW"] } }] } #endif QVariantMap clientMap; QVariantMap map; clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE")); clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps()); map.insert(QStringLiteral("client"), clientMap); QVariantList listUpdateRequests; QVariantMap threatMap; threatMap.insert(QStringLiteral("platformType"), QStringLiteral("WINDOWS")); threatMap.insert(QStringLiteral("threatType"), QStringLiteral("MALWARE")); threatMap.insert(QStringLiteral("threatEntryType"), QStringLiteral("URL")); //Contrainsts QVariantMap contraintsMap; QVariantList contraintsCompressionList; QString compressionStr; switch (d->mContraintsCompressionType) { case RiceCompression: compressionStr = QStringLiteral("RICE"); break; case RawCompression: compressionStr = QStringLiteral("RAW"); break; } contraintsCompressionList.append(compressionStr); contraintsMap.insert(QStringLiteral("supportedCompressions"), contraintsCompressionList); threatMap.insert(QStringLiteral("constraints"), contraintsMap); //Define state when we want to define update database. Empty is full. switch (d->mDataBaseDownloadNeeded) { case FullDataBase: qCDebug(WEBENGINEVIEWER_LOG) << " full update"; threatMap.insert(QStringLiteral("state"), QString()); break; case UpdateDataBase: qCDebug(WEBENGINEVIEWER_LOG) << " update database"; if (d->mDataBaseState.isEmpty()) { qCWarning(WEBENGINEVIEWER_LOG) << "Partial Download asked but database set is empty"; } threatMap.insert(QStringLiteral("state"), d->mDataBaseState); break; } listUpdateRequests.append(threatMap); map.insert(QStringLiteral("listUpdateRequests"), listUpdateRequests); const QJsonDocument postData = QJsonDocument::fromVariant(map); const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_CreatePhishingUrlDataBaseJob ? QJsonDocument::Compact : QJsonDocument::Indented); return baPostData; } void CreatePhishingUrlDataBaseJob::setDataBaseDownloadNeeded(CreatePhishingUrlDataBaseJob::DataBaseDownloadType type) { d->mDataBaseDownloadNeeded = type; } void CreatePhishingUrlDataBaseJob::slotDownloadDataBaseFinished(QNetworkReply *reply) { const QByteArray returnValue(reply->readAll()); Q_EMIT debugJsonResult(returnValue); parseResult(returnValue); reply->deleteLater(); } RiceDeltaEncoding CreatePhishingUrlDataBaseJobPrivate::parseRiceDeltaEncoding(const QMap &map) { RiceDeltaEncoding riceDeltaEncodingTmp; QMap::const_iterator riceHashesIt = map.cbegin(); const QMap::const_iterator riceHashesItEnd = map.cend(); for (; riceHashesIt != riceHashesItEnd; ++riceHashesIt) { const QString key = riceHashesIt.key(); if (key == QLatin1String("firstValue")) { riceDeltaEncodingTmp.firstValue = riceHashesIt.value().toByteArray(); } else if (key == QLatin1String("riceParameter")) { riceDeltaEncodingTmp.riceParameter = riceHashesIt.value().toInt(); } else if (key == QLatin1String("numEntries")) { riceDeltaEncodingTmp.numberEntries = riceHashesIt.value().toInt(); } else if (key == QLatin1String("encodedData")) { riceDeltaEncodingTmp.encodingData = riceHashesIt.value().toByteArray(); } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRiceDeltaEncoding unknown riceDeltaEncoding key " << key; } } return riceDeltaEncodingTmp; } QVector CreatePhishingUrlDataBaseJobPrivate::parseAdditions(const QVariantList &lst) { QVector additionList; for (const QVariant &v : lst) { if (v.canConvert()) { QMapIterator mapIt(v.toMap()); Addition tmp; while (mapIt.hasNext()) { mapIt.next(); const QString keyStr = mapIt.key(); if (keyStr == QLatin1String("compressionType")) { tmp.compressionType = parseCompressionType(mapIt.value().toString()); } else if (keyStr == QLatin1String("riceHashes")) { RiceDeltaEncoding riceDeltaEncodingTmp = parseRiceDeltaEncoding(mapIt.value().toMap()); if (riceDeltaEncodingTmp.isValid()) { tmp.riceDeltaEncoding = riceDeltaEncodingTmp; } } else if (keyStr == QLatin1String("rawHashes")) { QMapIterator rawHashesIt(mapIt.value().toMap()); while (rawHashesIt.hasNext()) { rawHashesIt.next(); const QString key = rawHashesIt.key(); if (key == QLatin1String("rawHashes")) { tmp.hashString = QByteArray::fromBase64(rawHashesIt.value().toByteArray()); } else if (key == QLatin1String("prefixSize")) { tmp.prefixSize = rawHashesIt.value().toInt(); } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions unknown rawHashes key " << key; } } } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions unknown mapIt.key() " << keyStr; } } if (tmp.isValid()) { additionList.append(tmp); } } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions not parsing type " << v.typeName(); } } return additionList; } UpdateDataBaseInfo::CompressionType CreatePhishingUrlDataBaseJobPrivate::parseCompressionType(const QString &str) { UpdateDataBaseInfo::CompressionType type(UpdateDataBaseInfo::UnknownCompression); if (str == QLatin1String("COMPRESSION_TYPE_UNSPECIFIED")) { type = UpdateDataBaseInfo::UnknownCompression; } else if (str == QLatin1String("RICE")) { type = UpdateDataBaseInfo::RiceCompression; } else if (str == QLatin1String("RAW")) { type = UpdateDataBaseInfo::RawCompression; } else { qCWarning(WEBENGINEVIEWER_LOG) << "CreatePhishingUrlDataBaseJob::parseCompressionType unknown compression type " << str; } return type; } QVector CreatePhishingUrlDataBaseJobPrivate::parseRemovals(const QVariantList &lst) { QVector removalList; for (const QVariant &v : lst) { if (v.canConvert()) { Removal tmp; QMapIterator mapIt(v.toMap()); while (mapIt.hasNext()) { mapIt.next(); const QString keyStr = mapIt.key(); if (keyStr == QLatin1String("compressionType")) { tmp.compressionType = parseCompressionType(mapIt.value().toString()); } else if (keyStr == QLatin1String("riceIndices")) { RiceDeltaEncoding riceDeltaEncodingTmp = parseRiceDeltaEncoding(mapIt.value().toMap()); if (riceDeltaEncodingTmp.isValid()) { tmp.riceDeltaEncoding = riceDeltaEncodingTmp; } } else if (keyStr == QLatin1String("rawIndices")) { const QVariantMap map = mapIt.value().toMap(); QMapIterator rawIndicesIt(map); while (rawIndicesIt.hasNext()) { rawIndicesIt.next(); if (rawIndicesIt.key() == QStringLiteral("indices")) { const QVariantList lst = rawIndicesIt.value().toList(); QList indexList; for (const QVariant &var : lst) { indexList.append(var.toUInt()); } tmp.indexes = indexList; } else { qCDebug(WEBENGINEVIEWER_LOG) << "rawIndicesIt.key() unknown " << rawIndicesIt.key(); } } } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRemovals unknown mapIt.key() " << keyStr; } } if (tmp.isValid()) { removalList.append(tmp); } } else { qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRemovals not parsing type " << v.typeName(); } } return removalList; } void CreatePhishingUrlDataBaseJob::parseResult(const QByteArray &value) { UpdateDataBaseInfo databaseInfo; QJsonDocument document = QJsonDocument::fromJson(value); if (document.isNull()) { Q_EMIT finished(databaseInfo, InvalidData); } else { const QVariantMap answer = document.toVariant().toMap(); if (answer.isEmpty()) { Q_EMIT finished(databaseInfo, InvalidData); } else { QMapIterator i(answer); while (i.hasNext()) { i.next(); if (i.key() == QLatin1String("listUpdateResponses")) { const QVariantList info = i.value().toList(); if (info.count() == 1) { const QVariant infoVar = info.at(0); if (infoVar.canConvert()) { QMapIterator mapIt(infoVar.toMap()); while (mapIt.hasNext()) { mapIt.next(); const QString mapKey = mapIt.key(); if (mapKey == QLatin1String("additions")) { const QVariantList lst = mapIt.value().toList(); const QVector addList = d->parseAdditions(lst); if (!addList.isEmpty()) { databaseInfo.additionList.append(addList); } } else if (mapKey == QLatin1String("removals")) { const QVariantList lst = mapIt.value().toList(); const QVector removeList = d->parseRemovals(lst); if (!removeList.isEmpty()) { databaseInfo.removalList.append(removeList); } } else if (mapKey == QLatin1String("checksum")) { QMapIterator mapCheckSum(mapIt.value().toMap()); while (mapCheckSum.hasNext()) { mapCheckSum.next(); if (mapCheckSum.key() == QLatin1String("sha256")) { databaseInfo.sha256 = mapCheckSum.value().toByteArray(); } else { qCDebug(WEBENGINEVIEWER_LOG) << "Invalid checksum key" << mapCheckSum.key(); } } } else if (mapKey == QLatin1String("newClientState")) { databaseInfo.newClientState = mapIt.value().toString(); } else if (mapKey == QLatin1String("platformType")) { databaseInfo.platformType = mapIt.value().toString(); } else if (mapKey == QLatin1String("responseType")) { const QString str = mapIt.value().toString(); if (str == QLatin1String("FULL_UPDATE")) { databaseInfo.responseType = UpdateDataBaseInfo::FullUpdate; } else if (str == QLatin1String("PARTIAL_UPDATE")) { databaseInfo.responseType = UpdateDataBaseInfo::PartialUpdate; } else { qCDebug(WEBENGINEVIEWER_LOG) << " unknow responsetype " << str; databaseInfo.responseType = UpdateDataBaseInfo::Unknown; } } else if (mapKey == QLatin1String("threatEntryType")) { databaseInfo.threatEntryType = mapIt.value().toString(); } else if (mapKey == QLatin1String("threatType")) { databaseInfo.threatType = mapIt.value().toString(); } else { qCDebug(WEBENGINEVIEWER_LOG) << " unknow key " << mapKey; } } } } } else if (i.key() == QLatin1String("minimumWaitDuration")) { databaseInfo.minimumWaitDuration = i.value().toString(); } else { qCDebug(WEBENGINEVIEWER_LOG) << " map key unknown " << i.key(); } } Q_EMIT finished(databaseInfo, ValidData); } } deleteLater(); } void CreatePhishingUrlDataBaseJob::setContraintsCompressionType(CreatePhishingUrlDataBaseJob::ContraintsCompressionType type) { d->mContraintsCompressionType = type; } diff --git a/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp b/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp index 20d678bf..5a9894da 100644 --- a/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp +++ b/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp @@ -1,106 +1,104 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "localdatabasemanager.h" #include "localdatabasemanager_p.h" #include "webengineviewer_debug.h" #include "createphishingurldatabasejob.h" #include "createdatabasefilejob.h" #include "checkphishingurlutil.h" #include "urlhashing.h" #include "backoffmodemanager.h" #include #include #include #include using namespace WebEngineViewer; Q_GLOBAL_STATIC(LocalDataBaseManagerPrivate, s_localDataBaseManager) LocalDataBaseManager::LocalDataBaseManager(LocalDataBaseManagerPrivate *impl, QObject *parent) : QObject(parent), d(impl) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } LocalDataBaseManager::LocalDataBaseManager(QObject *parent) : LocalDataBaseManager(s_localDataBaseManager, parent) { } LocalDataBaseManager::~LocalDataBaseManager() { } void LocalDataBaseManager::initialize() { d->initialize(); } void LocalDataBaseManager::checkUrl(const QUrl &url) { if (d->mDataBaseOk) { //TODO fixme short hash! we don't need to use it. WebEngineViewer::UrlHashing urlHashing(url); QHash hashList = urlHashing.hashList(); QHash conflictHashs; QHashIterator i(hashList); while (i.hasNext()) { i.next(); const QByteArray ba = i.value(); QByteArray result = d->mFile.searchHash(ba); if (ba.contains(result)) { conflictHashs.insert(i.key().toBase64(), ba.toBase64()); } } if (conflictHashs.isEmpty()) { Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Ok); } else { qCWarning(WEBENGINEVIEWER_LOG) << " We need to Check Server Database"; if (d->mNewClientState.isEmpty()) { qCWarning(WEBENGINEVIEWER_LOG) << "Database client state is unknown"; Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Unknown); } else { WebEngineViewer::SearchFullHashJob *job = new WebEngineViewer::SearchFullHashJob(this); job->setDatabaseState(QStringList() << d->mNewClientState); job->setSearchHashs(conflictHashs); job->setSearchFullHashForUrl(url); - connect(job, &SearchFullHashJob::result, - this, [this](CheckPhishingUrlUtil::UrlStatus status, const QUrl &url) { - Q_EMIT checkUrlFinished(url, status); + connect(job, &SearchFullHashJob::result, this, [this](CheckPhishingUrlUtil::UrlStatus status, const QUrl &url) { Q_EMIT checkUrlFinished(url, status); }); job->start(); } } } else { qCWarning(WEBENGINEVIEWER_LOG) << "Database not ok"; Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Unknown); } if (d->mFile.checkFileChanged()) { d->mFile.reload(); } } diff --git a/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp b/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp index ee215fbc..85103964 100644 --- a/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp +++ b/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp @@ -1,289 +1,289 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "searchfullhashjob.h" #include "checkphishingurlutil.h" #include #include #include #include #include using namespace WebEngineViewer; WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob = true; class WebEngineViewer::SearchFullHashJobPrivate { public: SearchFullHashJobPrivate() : mNetworkAccessManager(nullptr) { } bool foundExactHash(const QList &listLongHash); QHash mHashs; QUrl mUrl; QStringList mDatabaseHashes; QNetworkAccessManager *mNetworkAccessManager; }; SearchFullHashJob::SearchFullHashJob(QObject *parent) : QObject(parent), d(new SearchFullHashJobPrivate) { d->mNetworkAccessManager = new QNetworkAccessManager(this); connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &SearchFullHashJob::slotCheckUrlFinished); connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &SearchFullHashJob::slotSslErrors); } SearchFullHashJob::~SearchFullHashJob() { delete d; } void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList &error) { qCDebug(WEBENGINEVIEWER_LOG) << " void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList &error)" << error.count(); reply->ignoreSslErrors(error); } void SearchFullHashJob::parse(const QByteArray &replyStr) { /* { "matches": [{ "threatType": "MALWARE", "platformType": "WINDOWS", "threatEntryType": "URL", "threat": { "hash": "WwuJdQx48jP-4lxr4y2Sj82AWoxUVcIRDSk1PC9Rf-4=" }, "threatEntryMetadata": { "entries": [{ "key": "bWFsd2FyZV90aHJlYXRfdHlwZQ==", // base64-encoded "malware_threat_type" "value": "TEFORElORw==" // base64-encoded "LANDING" }] }, "cacheDuration": "300.000s" }, { "threatType": "SOCIAL_ENGINEERING", "platformType": "WINDOWS", "threatEntryType": "URL", "threat": { "hash": "771MOrRPMn6xPKlCrXx_CrR-wmCk0LgFFoSgGy7zUiA=" }, "threatEntryMetadata": { "entries": [] }, "cacheDuration": "300.000s" }], "minimumWaitDuration": "300.000s", "negativeCacheDuration": "300.000s" } */ QJsonDocument document = QJsonDocument::fromJson(replyStr); if (document.isNull()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl); } else { const QVariantMap answer = document.toVariant().toMap(); if (answer.isEmpty()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl); return; } else { const QVariantList info = answer.value(QStringLiteral("matches")).toList(); //TODO const QString minimumWaitDuration = answer.value(QStringLiteral("minimumWaitDuration")).toString(); const QString negativeCacheDuration = answer.value(QStringLiteral("negativeCacheDuration")).toString(); //Implement multi match ? if (info.count() == 1) { const QVariantMap map = info.at(0).toMap(); const QString threatTypeStr = map[QStringLiteral("threatType")].toString(); const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString(); if (threatTypeStr == QStringLiteral("MALWARE")) { const QVariantMap urlMap = map[QStringLiteral("threat")].toMap(); QList hashList; QMap::const_iterator urlMapIt = urlMap.cbegin(); const QMap::const_iterator urlMapItEnd = urlMap.cend(); for (; urlMapIt != urlMapItEnd; ++urlMapIt) { const QByteArray hashStr = urlMapIt.value().toByteArray(); hashList << hashStr; } if (d->foundExactHash(hashList)) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl); } else { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl); } const QVariantMap threatEntryMetadataMap = map[QStringLiteral("threatEntryMetadata")].toMap(); if (!threatEntryMetadataMap.isEmpty()) { //TODO } } else { qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse threatTypeStr : " << threatTypeStr; } } else { qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse matches multi element : " << info.count(); Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl); } } } deleteLater(); } bool SearchFullHashJobPrivate::foundExactHash(const QList &listLongHash) { const QList lstLongHash = mHashs.keys(); for (const QByteArray &ba : lstLongHash) { if (listLongHash.contains(ba)) { return true; } } return false; } void SearchFullHashJob::slotCheckUrlFinished(QNetworkReply *reply) { parse(reply->readAll()); reply->deleteLater(); } void SearchFullHashJob::setSearchHashs(const QHash &hash) { d->mHashs = hash; } QByteArray SearchFullHashJob::jsonRequest() const { /* { "client": { "clientId": "yourcompanyname", "clientVersion": "1.5.2" }, "clientStates": [ "ChAIARABGAEiAzAwMSiAEDABEAE=", "ChAIAhABGAEiAzAwMSiAEDABEOgH" ], "threatInfo": { "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"], "platformTypes": ["WINDOWS"], "threatEntryTypes": ["URL"], "threatEntries": [ {"hash": "WwuJdQ=="}, {"hash": "771MOg=="}, {"hash": "5eOrwQ=="} ] } } */ QVariantMap clientMap; QVariantMap map; clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE")); clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps()); map.insert(QStringLiteral("client"), clientMap); //clientStates We can support multi database. QVariantList clientStatesList; for (const QString &str : qAsConst(d->mDatabaseHashes)) { if (!str.isEmpty()) { clientStatesList.append(str); } } map.insert(QStringLiteral("clientStates"), clientStatesList); QVariantMap threatMap; QVariantList platformList; platformList.append(QStringLiteral("WINDOWS")); threatMap.insert(QStringLiteral("platformTypes"), platformList); const QVariantList threatTypesList = { QStringLiteral("MALWARE") }; threatMap.insert(QStringLiteral("threatTypes"), threatTypesList); const QVariantList threatEntryTypesList = { QStringLiteral("URL") }; threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList); QVariantList threatEntriesList; QVariantMap hashUrlMap; QHashIterator i(d->mHashs); while (i.hasNext()) { i.next(); hashUrlMap.insert(QStringLiteral("hash"), i.value()); } threatEntriesList.append(hashUrlMap); threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList); map.insert(QStringLiteral("threatInfo"), threatMap); const QJsonDocument postData = QJsonDocument::fromVariant(map); const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_SearchFullHashJob ? QJsonDocument::Compact : QJsonDocument::Indented); return baPostData; } void SearchFullHashJob::start() { if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl); deleteLater(); } else if (canStart()) { QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/fullHashes:find")); safeUrl.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey()); QNetworkRequest request(safeUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); const QByteArray baPostData = jsonRequest(); //qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData; Q_EMIT debugJson(baPostData); QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData); - connect(reply, static_cast(&QNetworkReply::error), this, &SearchFullHashJob::slotError); + connect(reply, QOverload::of(&QNetworkReply::error), this, &SearchFullHashJob::slotError); } else { Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl); deleteLater(); } } void SearchFullHashJob::slotError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString(); reply->deleteLater(); deleteLater(); } bool SearchFullHashJob::canStart() const { return !d->mHashs.isEmpty() && !d->mDatabaseHashes.isEmpty() && !d->mUrl.isEmpty(); } void SearchFullHashJob::setDatabaseState(const QStringList &hash) { d->mDatabaseHashes = hash; } void SearchFullHashJob::setSearchFullHashForUrl(const QUrl &url) { d->mUrl = url; }