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 }