File indexing completed on 2025-03-09 04:54:26

0001 /*
0002     spamheaderanalyzer.cpp
0003 
0004     This file is part of KMail, the KDE mail client.
0005     SPDX-FileCopyrightText: 2004 Patrick Audley <paudley@blackcat.ca>
0006     SPDX-FileCopyrightText: 2004 Ingo Kloecker <kloecker@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "spamheaderanalyzer.h"
0012 #include "antispamconfig.h"
0013 #include "messageviewer_debug.h"
0014 
0015 #include <KMime/Headers>
0016 #include <KMime/KMimeMessage>
0017 #include <KMime/Message>
0018 
0019 #include <QRegularExpression>
0020 
0021 using namespace MessageViewer;
0022 
0023 // static
0024 SpamScores SpamHeaderAnalyzer::getSpamScores(KMime::Message *message)
0025 {
0026     SpamScores scores;
0027     const SpamAgents agents = AntiSpamConfig::instance()->uniqueAgents();
0028     SpamAgents::const_iterator end(agents.constEnd());
0029     for (SpamAgents::const_iterator it = agents.constBegin(); it != end; ++it) {
0030         float score = -2.0;
0031 
0032         SpamError spamError = noError;
0033 
0034         // Skip bogus agents
0035         if ((*it).scoreType() == SpamAgentNone) {
0036             continue;
0037         }
0038 
0039         // Do we have the needed score field for this agent?
0040         KMime::Headers::Base *header = message->headerByType((*it).header().constData());
0041         if (!header) {
0042             continue;
0043         }
0044 
0045         const QString mField = header->asUnicodeString();
0046 
0047         if (mField.isEmpty()) {
0048             continue;
0049         }
0050 
0051         QString scoreString;
0052         bool scoreValid = false;
0053 
0054         if ((*it).scoreType() != SpamAgentBool) {
0055             // Can we extract the score?
0056             QRegularExpression scorePattern = (*it).scorePattern();
0057             if (scorePattern.match(mField).hasMatch()) {
0058                 scoreString = scorePattern.match(mField).captured(1);
0059                 scoreValid = true;
0060             }
0061         } else {
0062             scoreValid = true;
0063         }
0064 
0065         if (!scoreValid) {
0066             spamError = couldNotFindTheScoreField;
0067             qCDebug(MESSAGEVIEWER_LOG) << "Score could not be extracted from header '" << mField << "'";
0068         } else {
0069             bool floatValid = false;
0070             switch ((*it).scoreType()) {
0071             case SpamAgentNone:
0072                 spamError = errorExtractingAgentString;
0073                 break;
0074 
0075             case SpamAgentBool:
0076                 if ((*it).scorePattern().match(mField).hasMatch()) {
0077                     score = 0.0;
0078                 } else {
0079                     score = 100.0;
0080                 }
0081                 break;
0082 
0083             case SpamAgentFloat:
0084                 score = scoreString.toFloat(&floatValid);
0085                 if (!floatValid) {
0086                     spamError = couldNotConverScoreToFloat;
0087                     qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
0088                 } else {
0089                     score *= 100.0;
0090                 }
0091                 break;
0092 
0093             case SpamAgentFloatLarge:
0094                 score = scoreString.toFloat(&floatValid);
0095                 if (!floatValid) {
0096                     spamError = couldNotConverScoreToFloat;
0097                     qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
0098                 }
0099                 break;
0100 
0101             case SpamAgentAdjustedFloat:
0102                 score = scoreString.toFloat(&floatValid);
0103                 if (!floatValid) {
0104                     spamError = couldNotConverScoreToFloat;
0105                     qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
0106                     break;
0107                 }
0108 
0109                 // Find the threshold value.
0110                 QString thresholdString;
0111                 const QRegularExpression thresholdPattern = (*it).thresholdPattern();
0112                 if (thresholdPattern.match(mField).hasMatch()) {
0113                     thresholdString = thresholdPattern.match(mField).captured(1);
0114                 } else {
0115                     spamError = couldNotFindTheThresholdField;
0116                     qCDebug(MESSAGEVIEWER_LOG) << "Threshold could not be extracted from header '" << mField << "'";
0117                     break;
0118                 }
0119                 const float threshold = thresholdString.toFloat(&floatValid);
0120                 if (!floatValid || (threshold <= 0.0)) {
0121                     spamError = couldNotConvertThresholdToFloatOrThresholdIsNegative;
0122                     qCDebug(MESSAGEVIEWER_LOG) << "Threshold (" << thresholdString << ") is no"
0123                                                << "number or is negative";
0124                     break;
0125                 }
0126 
0127                 // Normalize the score. Anything below 0 means 0%, anything above
0128                 // threshold mean 100%. Values between 0 and threshold are mapped
0129                 // linearly to 0% - 100%.
0130                 if (score < 0.0) {
0131                     score = 0.0;
0132                 } else if (score > threshold) {
0133                     score = 100.0;
0134                 } else {
0135                     score = score / threshold * 100.0;
0136                 }
0137                 break;
0138             }
0139         }
0140         // Find the confidence
0141         float confidence = -2.0;
0142         QString confidenceString = QStringLiteral("-2.0");
0143         bool confidenceValid = false;
0144         // Do we have the needed confidence field for this agent?
0145         const QByteArray confidenceHeaderName = (*it).confidenceHeader();
0146         QString mCField;
0147         if (!confidenceHeaderName.isEmpty()) {
0148             KMime::Headers::Base *cHeader = message->headerByType(confidenceHeaderName.constData());
0149             if (cHeader) {
0150                 mCField = cHeader->asUnicodeString();
0151                 if (!mCField.isEmpty()) {
0152                     // Can we extract the confidence?
0153                     QRegularExpression cScorePattern = (*it).confidencePattern();
0154                     if (cScorePattern.match(mCField).hasMatch()) {
0155                         confidenceString = cScorePattern.match(mCField).captured(1);
0156                     }
0157                     confidence = confidenceString.toFloat(&confidenceValid);
0158                     if (!confidenceValid) {
0159                         spamError = couldNotConvertConfidenceToFloat;
0160                         qCDebug(MESSAGEVIEWER_LOG) << "Unable to convert confidence to float:" << confidenceString;
0161                     }
0162                 }
0163             }
0164         }
0165         scores.append(SpamScore((*it).name(), spamError, score, confidence * 100, mField, mCField));
0166     }
0167 
0168     return scores;
0169 }