File indexing completed on 2024-04-28 05:50:15

0001 /*
0002     SPDX-FileCopyrightText: 2008, 2009, 2010, 2012, 2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
0003     SPDX-FileCopyrightText: 2013 Thomas Fischer <fischer@unix-ag.uni-kl.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 #include "keylistproxymodel.h"
0008 #include "model/kgpgitemnode.h"
0009 #include "kgpgitemmodel.h"
0010 #include "kgpgsettings.h"
0011 #include "core/images.h"
0012 
0013 #include <KLocalizedString>
0014 #include <QDate>
0015 
0016 using namespace KgpgCore;
0017 
0018 class KeyListProxyModelPrivate {
0019     KeyListProxyModel * const q_ptr;
0020 
0021     Q_DECLARE_PUBLIC(KeyListProxyModel)
0022 public:
0023     KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode);
0024 
0025     bool lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const;
0026     bool nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const;
0027     KGpgItemModel *m_model;
0028     bool m_onlysecret;
0029     bool m_encryptionKeys;
0030     KgpgCore::KgpgKeyTrustFlag m_mintrust;
0031     int m_previewsize;
0032     int m_idLength;
0033     KeyListProxyModel::DisplayMode m_displaymode;
0034     int m_emailSorting;
0035 
0036     QString reorderEmailComponents(const QString &emailAddress) const;
0037     QVariant dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const;
0038     QVariant dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const;
0039 };
0040 
0041 KeyListProxyModelPrivate::KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode)
0042     : q_ptr(parent),
0043     m_model(nullptr),
0044     m_onlysecret(false),
0045     m_encryptionKeys(false),
0046     m_mintrust(TRUST_UNKNOWN),
0047     m_previewsize(22),
0048     m_idLength(8),
0049     m_displaymode(mode),
0050     m_emailSorting(KGpgSettings::emailSorting())
0051 {
0052 }
0053 
0054 /**
0055  * Reverses the list's order (this modifies the list) and returns
0056  * a string containing the reversed list's elements joined by a char.
0057  */
0058 static QString reverseListAndJoinWithChar(const QStringList &list, const QChar &separator)
0059 {
0060     QString result = list.last();
0061     for (int i = list.count() - 2; i >= 0; --i)
0062         result.append(separator).append(list[i]);
0063     return result;
0064 }
0065 
0066 QString
0067 KeyListProxyModelPrivate::reorderEmailComponents(const QString &emailAddress) const
0068 {
0069     if (emailAddress.isEmpty())
0070         return QString();
0071 
0072     /// split email addresses at @
0073     static const QChar charAt = QLatin1Char('@');
0074     /// split domain at .
0075     static const QChar charDot = QLatin1Char('.');
0076 
0077     QString result = emailAddress;
0078 
0079     switch (m_emailSorting) {
0080     case KGpgSettings::EnumEmailSorting::TLDfirst:
0081     {
0082         /// get components of an email address
0083         /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
0084         const QStringList emailComponents = result.split(charAt);
0085         if (emailComponents.count() != 2) /// expect an email address to contain exactly one @
0086             break;
0087         /// get components of a domain
0088         /// mail.kde.org becomes [mail, kde, org]
0089         const QString fqdn = emailComponents.last();
0090         QStringList fqdnComponents = fqdn.split(charDot);
0091         if (fqdnComponents.count() < 2) /// if domain consists of less than two components ...
0092             return fqdn + charDot + emailComponents.first(); /// ... take shortcut
0093         /// prepend localpart, will be last after list is reversed
0094         fqdnComponents.insert(0, emailComponents.first());
0095         /// reverse components of domain, result becomes e.g. org.kde.mail
0096         /// with localpart already in the list it becomes org.kde.mail.john.doe
0097         result = reverseListAndJoinWithChar(fqdnComponents, charDot);
0098         break;
0099     }
0100     case KGpgSettings::EnumEmailSorting::DomainFirst:
0101     {
0102         /// get components of an email address
0103         /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
0104         const QStringList emailComponents = result.split(charAt);
0105         if (emailComponents.count() != 2) /// expect an email address to contain exactly one @
0106             break;
0107         /// get components of a domain
0108         /// mail.kde.org becomes [mail, kde, org]
0109         const QString fqdn = emailComponents.last();
0110         QStringList fqdnComponents = fqdn.split(charDot);
0111         if (fqdnComponents.count() < 2) /// if domain consists of less than two components ...
0112             return fqdn + charDot + emailComponents.first(); /// ... take shortcut
0113         /// reverse last two components of domain, becomes e.g. kde.org
0114         /// TODO will fail for three-part domains like kde.org.uk
0115         result = charDot + fqdnComponents.takeLast();
0116         result.prepend(fqdnComponents.takeLast());
0117         /// append remaining components of domain, becomes e.g. kde.org.mail
0118         result.append(charDot).append(fqdnComponents.join(charDot));
0119         /// append user name component of email address, becomes e.g. kde.org.mail.john.doe
0120         result.append(charDot).append(emailComponents.first());
0121         break;
0122     }
0123     case KGpgSettings::EnumEmailSorting::FQDNFirst:
0124     {
0125         /// get components of an email address
0126         /// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
0127         const QStringList emailComponents = result.split(charAt);
0128         /// assemble result by joining components in reverse order,
0129         /// separated by a dot, becomes e.g. mail.kde.org.john.doe
0130         result = reverseListAndJoinWithChar(emailComponents, charDot);
0131         break;
0132     }
0133     case KGpgSettings::EnumEmailSorting::Alphabetical:
0134         /// do not modify email address
0135         break;
0136     }
0137 
0138     return result;
0139 }
0140 
0141 QVariant
0142 KeyListProxyModelPrivate::dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const
0143 {
0144     Q_Q(const KeyListProxyModel);
0145 
0146     if (index.column() != 0)
0147         return QVariant();
0148 
0149     switch (role) {
0150     case Qt::DecorationRole:
0151         if (node->getType() == ITYPE_UAT) {
0152             if (m_previewsize > 0) {
0153                 const KGpgUatNode *nd = node->toUatNode();
0154                 return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio);
0155             } else {
0156                 return Images::photo();
0157             }
0158         } else {
0159             return m_model->data(q->mapToSource(index), Qt::DecorationRole);
0160         }
0161     case Qt::DisplayRole: {
0162         const QModelIndex srcidx(q->mapToSource(index));
0163         const int srcrow = srcidx.row();
0164 
0165         const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID));
0166         const QString id(m_model->data(ididx, Qt::DisplayRole).toString().right(m_idLength));
0167 
0168         const QModelIndex mailidx(srcidx.sibling(srcrow, KEYCOLUMN_EMAIL));
0169         const QString mail(m_model->data(mailidx, Qt::DisplayRole).toString());
0170 
0171         const QModelIndex nameidx(srcidx.sibling(srcrow, KEYCOLUMN_NAME));
0172         const QString name(m_model->data(nameidx, Qt::DisplayRole).toString());
0173 
0174         if (m_displaymode == KeyListProxyModel::SingleColumnIdFirst) {
0175             if (mail.isEmpty())
0176                 return i18nc("ID: Name", "%1: %2", id, name);
0177             else
0178                 return i18nc("ID: Name <Email>", "%1: %2 <%3>", id, name, mail);
0179         } else {
0180             if (mail.isEmpty())
0181                 return i18nc("Name: ID", "%1: %2", name, id);
0182             else
0183                 return i18nc("Name <Email>: ID", "%1 <%2>: %3", name, mail, id);
0184         }
0185         }
0186     case Qt::ToolTipRole: {
0187         const QModelIndex srcidx(q->mapToSource(index));
0188         const int srcrow = srcidx.row();
0189 
0190         const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID));
0191         return m_model->data(ididx, Qt::DisplayRole);
0192         }
0193     default:
0194         return QVariant();
0195     }
0196 }
0197 
0198 QVariant
0199 KeyListProxyModelPrivate::dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const
0200 {
0201     Q_Q(const KeyListProxyModel);
0202 
0203     if ((node->getType() == ITYPE_UAT) && (role == Qt::DecorationRole) && (index.column() == 0)) {
0204         if (m_previewsize > 0) {
0205             const KGpgUatNode *nd = node->toUatNode();
0206             return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio);
0207         } else {
0208             return Images::photo();
0209         }
0210     } else if ((role == Qt::DisplayRole) && (index.column() == KEYCOLUMN_ID)) {
0211         QString id = m_model->data(q->mapToSource(index), Qt::DisplayRole).toString();
0212         return id.right(m_idLength);
0213     }
0214     return m_model->data(q->mapToSource(index), role);
0215 }
0216 
0217 KeyListProxyModel::KeyListProxyModel(QObject *parent, const DisplayMode mode)
0218     : QSortFilterProxyModel(parent),
0219     d_ptr(new KeyListProxyModelPrivate(this, mode))
0220 {
0221     setFilterCaseSensitivity(Qt::CaseInsensitive);
0222     setFilterKeyColumn(-1);
0223     setDynamicSortFilter(true);
0224 }
0225 
0226 KeyListProxyModel::~KeyListProxyModel()
0227 {
0228     delete d_ptr;
0229 }
0230 
0231 bool
0232 KeyListProxyModel::hasChildren(const QModelIndex &idx) const
0233 {
0234     return sourceModel()->hasChildren(mapToSource(idx));
0235 }
0236 
0237 void
0238 KeyListProxyModel::setKeyModel(KGpgItemModel *md)
0239 {
0240     Q_D(KeyListProxyModel);
0241 
0242     d->m_model = md;
0243     setSourceModel(md);
0244 }
0245 
0246 bool
0247 KeyListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0248 {
0249     Q_D(const KeyListProxyModel);
0250 
0251     KGpgNode *l = d->m_model->nodeForIndex(left);
0252     KGpgNode *r = d->m_model->nodeForIndex(right);
0253 
0254     return d->lessThan(l, r, left.column());
0255 }
0256 
0257 bool
0258 KeyListProxyModelPrivate::lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const
0259 {
0260     const KGpgRootNode * const r = m_model->getRootNode();
0261     Q_ASSERT(r != left);
0262     Q_ASSERT(r != right);
0263 
0264     if (r == left->getParentKeyNode()) {
0265         if (r == right->getParentKeyNode()) {
0266             if (left->getType() == ITYPE_GROUP) {
0267                 if (right->getType() == ITYPE_GROUP)
0268                     return left->getName() < right->getName();
0269                 else
0270                     return true;
0271             } else if (right->getType() == ITYPE_GROUP)
0272                 return false;
0273 
0274             // we don't need to care about group members here because they will never have root as parent
0275             bool test1 = (left->getType() & ITYPE_PUBLIC) && !(left->getType() & ITYPE_SECRET); // only a public key
0276             bool test2 = (right->getType() & ITYPE_PUBLIC) && !(right->getType() & ITYPE_SECRET); // only a public key
0277 
0278             // key-pair goes before simple public key
0279             // extra check needed to get sorting by trust right
0280             if (left->getType() == ITYPE_PAIR && test2) return (column != KEYCOLUMN_TRUST);
0281             if (right->getType() == ITYPE_PAIR && test1) return (column == KEYCOLUMN_TRUST);
0282 
0283             return nodeLessThan(left, right, column);
0284         } else {
0285             return lessThan(left, right->getParentKeyNode(), column);
0286         }
0287     } else {
0288         if (r == right->getParentKeyNode()) {
0289             return lessThan(left->getParentKeyNode(), right, column);
0290         } else if (left->getParentKeyNode() == right->getParentKeyNode()) {
0291             if (left->getType() != right->getType())
0292                 return (left->getType() < right->getType());
0293 
0294             return nodeLessThan(left, right, column);
0295         } else {
0296             return lessThan(left->getParentKeyNode(), right->getParentKeyNode(), column);
0297         }
0298     }
0299     return false;
0300 }
0301 
0302 bool
0303 KeyListProxyModelPrivate::nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const
0304 {
0305     Q_ASSERT(left->getType() == right->getType());
0306 
0307     switch (column) {
0308     case KEYCOLUMN_NAME:
0309         if (left->getType() == ITYPE_SIGN) {
0310             const bool leftIsId = static_cast<const KGpgSignNode *>(left)->getRefNode() == nullptr;
0311             const bool rightIsId = static_cast<const KGpgSignNode *>(right)->getRefNode() == nullptr;
0312             if (leftIsId && !rightIsId)
0313                 return false;
0314             else if (!leftIsId && rightIsId)
0315                 return true;
0316             else if (leftIsId && rightIsId)
0317                 return (left->getId() < right->getId());
0318         }
0319         return (left->getName().compare(right->getName(), Qt::CaseInsensitive) < 0);
0320     case KEYCOLUMN_EMAIL:
0321         /// reverse email address to sort by TLD first, then domain, and account name last
0322         return (reorderEmailComponents(left->getEmail()).compare(reorderEmailComponents(right->getEmail()), Qt::CaseInsensitive) < 0);
0323     case KEYCOLUMN_TRUST:
0324         return (left->getTrust() < right->getTrust());
0325     case KEYCOLUMN_EXPIR:
0326         return (left->getExpiration() < right->getExpiration());
0327     case KEYCOLUMN_SIZE:
0328         if ((left->getType() & ITYPE_PAIR) && (right->getType() & ITYPE_PAIR)) {
0329             unsigned int lsign, lenc, rsign, renc;
0330 
0331             if (left->getType() & ITYPE_GROUP) {
0332                 const KGpgGroupMemberNode *g = static_cast<const KGpgGroupMemberNode *>(left);
0333 
0334                 lsign = g->getSignKeySize();
0335                 lenc = g->getEncryptionKeySize();
0336             } else {
0337                 const KGpgKeyNode *g = static_cast<const KGpgKeyNode *>(left);
0338 
0339                 lsign = g->getSignKeySize();
0340                 lenc = g->getEncryptionKeySize();
0341             }
0342 
0343             if (right->getType() & ITYPE_GROUP) {
0344                 const KGpgGroupMemberNode *g = static_cast<const KGpgGroupMemberNode *>(right);
0345 
0346                 rsign = g->getSignKeySize();
0347                 renc = g->getEncryptionKeySize();
0348             } else {
0349                 const KGpgKeyNode *g = static_cast<const KGpgKeyNode *>(right);
0350 
0351                 rsign = g->getSignKeySize();
0352                 renc = g->getEncryptionKeySize();
0353             }
0354 
0355             if (lsign != rsign)
0356                 return lsign < rsign;
0357             else
0358                 return lenc < renc;
0359         } else {
0360             return (left->getSize() < right->getSize());
0361         }
0362     case KEYCOLUMN_CREAT:
0363         return (left->getCreation() < right->getCreation());
0364     default:
0365         Q_ASSERT(column == KEYCOLUMN_ID);
0366         return (QStringView(left->getId()).right(m_idLength).compare(QStringView(right->getId()).right(m_idLength)) < 0);
0367     }
0368 }
0369 
0370 bool
0371 KeyListProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0372 {
0373     Q_D(const KeyListProxyModel);
0374     QModelIndex idx = d->m_model->index(source_row, 0, source_parent);
0375     const KGpgNode *l = d->m_model->nodeForIndex(idx);
0376 
0377     if (l == d->m_model->getRootNode())
0378         return false;
0379 
0380     if (d->m_onlysecret) {
0381         switch (l->getType()) {
0382         case ITYPE_PUBLIC:
0383         case ITYPE_GPUBLIC:
0384         case ITYPE_GROUP:
0385             return false;
0386         default:
0387             break;
0388         }
0389     }
0390 
0391     switch (d->m_displaymode) {
0392     case SingleColumnIdFirst:
0393     case SingleColumnIdLast:
0394         if (l->getType() == ITYPE_GROUP)
0395             return false;
0396     default:
0397         break;
0398     }
0399 
0400     if (l->getTrust() < d->m_mintrust)
0401         return false;
0402 
0403     /* check for expired signatures */
0404     if ((d->m_mintrust > TRUST_EXPIRED) && (l->getType() == ITYPE_SIGN)) {
0405         const QDateTime expDate = l->toSignNode()->getExpiration();
0406         if (expDate.isValid() && (expDate < QDateTime::currentDateTime()))
0407             return false;
0408     }
0409 
0410     if (l->getParentKeyNode() != d->m_model->getRootNode())
0411         return true;
0412 
0413     if (d->m_encryptionKeys && ((l->getType() & ITYPE_GROUP) == 0)) {
0414         if (!l->toKeyNode()->canEncrypt())
0415             return false;
0416     }
0417 
0418     if (l->getName().contains(filterRegularExpression()))
0419         return true;
0420 
0421     if (l->getEmail().contains(filterRegularExpression()))
0422         return true;
0423 
0424     if (l->getId().contains(filterRegularExpression()))
0425         return true;
0426 
0427     return false;
0428 }
0429 
0430 void
0431 KeyListProxyModel::setOnlySecret(const bool b)
0432 {
0433     Q_D(KeyListProxyModel);
0434 
0435     d->m_onlysecret = b;
0436     invalidateFilter();
0437 }
0438 
0439 void
0440 KeyListProxyModel::settingsChanged()
0441 {
0442     Q_D(KeyListProxyModel);
0443 
0444     const int newSort = KGpgSettings::emailSorting();
0445 
0446     if (newSort != d->m_emailSorting) {
0447         d->m_emailSorting = newSort;
0448         invalidate();
0449     }
0450 }
0451 
0452 void
0453 KeyListProxyModel::setTrustFilter(const KgpgCore::KgpgKeyTrustFlag t)
0454 {
0455     Q_D(KeyListProxyModel);
0456 
0457     d->m_mintrust = t;
0458     invalidateFilter();
0459 }
0460 
0461 void
0462 KeyListProxyModel::setEncryptionKeyFilter(bool b)
0463 {
0464     Q_D(KeyListProxyModel);
0465 
0466     d->m_encryptionKeys = b;
0467     invalidateFilter();
0468 }
0469 
0470 KGpgNode *
0471 KeyListProxyModel::nodeForIndex(const QModelIndex &index) const
0472 {
0473     Q_D(const KeyListProxyModel);
0474 
0475     return d->m_model->nodeForIndex(mapToSource(index));
0476 }
0477 
0478 QModelIndex
0479 KeyListProxyModel::nodeIndex(KGpgNode *node)
0480 {
0481     Q_D(KeyListProxyModel);
0482 
0483     return mapFromSource(d->m_model->nodeIndex(node));
0484 }
0485 
0486 void
0487 KeyListProxyModel::setPreviewSize(const int pixel)
0488 {
0489     Q_D(KeyListProxyModel);
0490 
0491     Q_EMIT layoutAboutToBeChanged();
0492     d->m_previewsize = pixel;
0493     Q_EMIT layoutChanged();
0494 }
0495 
0496 QVariant
0497 KeyListProxyModel::data(const QModelIndex &index, int role) const
0498 {
0499     Q_D(const KeyListProxyModel);
0500 
0501     if (!index.isValid())
0502         return QVariant();
0503 
0504     const KGpgNode *node = nodeForIndex(index);
0505 
0506     switch (d->m_displaymode) {
0507     case MultiColumn:
0508         return d->dataMultiColumn(index, role, node);
0509     case SingleColumnIdFirst:
0510     case SingleColumnIdLast:
0511         return d->dataSingleColumn(index, role, node);
0512     }
0513 
0514     Q_ASSERT(0);
0515 
0516     return QVariant();
0517 }
0518 
0519 KGpgItemModel *
0520 KeyListProxyModel::getModel() const
0521 {
0522     Q_D(const KeyListProxyModel);
0523 
0524     return d->m_model;
0525 }
0526 
0527 int
0528 KeyListProxyModel::idLength() const
0529 {
0530     Q_D(const KeyListProxyModel);
0531 
0532     return d->m_idLength;
0533 }
0534 
0535 void
0536 KeyListProxyModel::setIdLength(const int length)
0537 {
0538     Q_D(KeyListProxyModel);
0539 
0540     if (length == d->m_idLength)
0541         return;
0542 
0543     d->m_idLength = length;
0544     invalidate();
0545 }
0546 
0547 bool
0548 KeyListProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
0549 {
0550     Q_UNUSED(role);
0551 
0552     if (value.typeId() != QMetaType::QString)
0553         return false;
0554 
0555     KGpgNode *node = nodeForIndex(index);
0556 
0557     if (!node)
0558         return false;
0559 
0560     const QString newName = value.toString();
0561 
0562     if (newName.isEmpty() || (newName == node->getName()))
0563         return false;
0564 
0565     node->toGroupNode()->rename(newName);
0566 
0567     return true;
0568 }
0569 
0570 Qt::ItemFlags
0571 KeyListProxyModel::flags(const QModelIndex &index) const
0572 {
0573     KGpgNode *node = nodeForIndex(index);
0574     Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
0575 
0576     if ((node->getType() == ITYPE_GROUP) && (index.column() == KEYCOLUMN_NAME))
0577         flags |= Qt::ItemIsEditable;
0578 
0579     return flags;
0580 }