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

0001 /*
0002    SPDX-FileCopyrightText: 2018-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dkimutil.h"
0008 #include "messageviewer_dkimcheckerdebug.h"
0009 #include <QRegularExpression>
0010 
0011 QString MessageViewer::DKIMUtil::bodyCanonizationRelaxed(QString body)
0012 {
0013     /*
0014      * canonicalize the body using the relaxed algorithm
0015      * specified in Section 3.4.4 of RFC 6376
0016      */
0017     /*
0018         a.  Reduce whitespace:
0019 
0020             *  Ignore all whitespace at the end of lines.  Implementations
0021                 MUST NOT remove the CRLF at the end of the line.
0022 
0023             *  Reduce all sequences of WSP within a line to a single SP
0024                 character.
0025 
0026         b.  Ignore all empty lines at the end of the message body.  "Empty
0027             line" is defined in Section 3.4.3.  If the body is non-empty but
0028             does not end with a CRLF, a CRLF is added.  (For email, this is
0029             only possible when using extensions to SMTP or non-SMTP transport
0030             mechanisms.)
0031         */
0032 
0033     body.replace(QStringLiteral("\n"), QStringLiteral("\r\n"));
0034     static const QRegularExpression reg1(QStringLiteral("[ \t]+\r\n"));
0035     body.replace(reg1, QStringLiteral("\r\n"));
0036     static const QRegularExpression reg2(QStringLiteral("[ \t]+"));
0037     body.replace(reg2, QStringLiteral(" "));
0038     static const QRegularExpression reg3(QStringLiteral("((\r\n)+?)$"));
0039     body.replace(QRegularExpression(reg3), QStringLiteral("\r\n"));
0040     if (body == QLatin1StringView("\r\n")) {
0041         body.clear();
0042     }
0043     return body;
0044 }
0045 
0046 QString MessageViewer::DKIMUtil::bodyCanonizationSimple(QString body)
0047 {
0048     //    The "simple" body canonicalization algorithm ignores all empty lines
0049     //       at the end of the message body.  An empty line is a line of zero
0050     //       length after removal of the line terminator.  If there is no body or
0051     //       no trailing CRLF on the message body, a CRLF is added.  It makes no
0052     //       other changes to the message body.  In more formal terms, the
0053     //       "simple" body canonicalization algorithm converts "*CRLF" at the end
0054     //       of the body to a single "CRLF".
0055 
0056     //       Note that a completely empty or missing body is canonicalized as a
0057     //       single "CRLF"; that is, the canonicalized length will be 2 octets.
0058     body.replace(QStringLiteral("\n"), QStringLiteral("\r\n"));
0059     static const QRegularExpression reg(QStringLiteral("((\r\n)+)?$"));
0060     body.replace(reg, QStringLiteral("\r\n"));
0061     if (body.endsWith(QLatin1StringView("\r\n"))) { // Remove it from start
0062         body.chop(2);
0063     }
0064     if (body.isEmpty()) {
0065         body = QStringLiteral("\r\n");
0066     }
0067     return body;
0068 }
0069 
0070 QByteArray MessageViewer::DKIMUtil::generateHash(const QByteArray &body, QCryptographicHash::Algorithm algo)
0071 {
0072     return QCryptographicHash::hash(body, algo).toBase64();
0073 }
0074 
0075 QString MessageViewer::DKIMUtil::headerCanonizationSimple(const QString &headerName, const QString &headerValue)
0076 {
0077     // TODO verify it lower it ?
0078     return headerName + QLatin1Char(':') + headerValue;
0079 }
0080 
0081 QString MessageViewer::DKIMUtil::headerCanonizationRelaxed(const QString &headerName, const QString &headerValue, bool removeQuoteOnContentType)
0082 {
0083     //    The "relaxed" header canonicalization algorithm MUST apply the
0084     //       following steps in order:
0085 
0086     //       o  Convert all header field names (not the header field values) to
0087     //          lowercase.  For example, convert "SUBJect: AbC" to "subject: AbC".
0088 
0089     //       o  Unfold all header field continuation lines as described in
0090     //          [RFC5322]; in particular, lines with terminators embedded in
0091     //          continued header field values (that is, CRLF sequences followed by
0092     //          WSP) MUST be interpreted without the CRLF.  Implementations MUST
0093     //          NOT remove the CRLF at the end of the header field value.
0094 
0095     //       o  Convert all sequences of one or more WSP characters to a single SP
0096     //          character.  WSP characters here include those before and after a
0097     //          line folding boundary.
0098 
0099     //       o  Delete all WSP characters at the end of each unfolded header field
0100     //          value.
0101 
0102     //       o  Delete any WSP characters remaining before and after the colon
0103     //          separating the header field name from the header field value.  The
0104     //          colon separator MUST be retained.
0105     QString newHeaderName = headerName.toLower();
0106     QString newHeaderValue = headerValue;
0107     static const QRegularExpression reg1(QStringLiteral("\r\n[ \t]+"));
0108     newHeaderValue.replace(reg1, QStringLiteral(" "));
0109     static const QRegularExpression reg2(QStringLiteral("[ \t]+"));
0110     newHeaderValue.replace(reg2, QStringLiteral(" "));
0111     static const QRegularExpression reg3(QStringLiteral("[ \t]+\r\n"));
0112     newHeaderValue.replace(reg3, QStringLiteral("\r\n"));
0113     // Perhaps remove tab after headername and before value name
0114     // newHeaderValue.replace(QRegularExpression(QStringLiteral("[ \t]*:[ \t]")), QStringLiteral(":"));
0115     if (newHeaderName == QLatin1StringView("content-type") && removeQuoteOnContentType) { // Remove quote in charset
0116         if (newHeaderValue.contains(QLatin1StringView("charset=\""))) {
0117             newHeaderValue.remove(QLatin1Char('"'));
0118         }
0119     }
0120     // Remove extra space.
0121     newHeaderValue = newHeaderValue.trimmed();
0122     return newHeaderName + QLatin1Char(':') + newHeaderValue;
0123 }
0124 
0125 QString MessageViewer::DKIMUtil::cleanString(QString str)
0126 {
0127     // Move as static ?
0128     // WSP help pattern as specified in Section 2.8 of RFC 6376
0129     const QString pattWSP = QStringLiteral("[ \t]");
0130     // FWS help pattern as specified in Section 2.8 of RFC 6376
0131     const QString pattFWS = QStringLiteral("(?:") + pattWSP + QStringLiteral("*(?:\r\n)?") + pattWSP + QStringLiteral("+)");
0132     str.replace(QRegularExpression(pattFWS), QString());
0133     return str;
0134 }
0135 
0136 QString MessageViewer::DKIMUtil::emailDomain(const QString &emailDomain)
0137 {
0138     return emailDomain.right(emailDomain.length() - emailDomain.indexOf(QLatin1Char('@')) - 1);
0139 }
0140 
0141 QString MessageViewer::DKIMUtil::emailSubDomain(const QString &emailDomain)
0142 {
0143     int dotNumber = 0;
0144     for (int i = emailDomain.length() - 1; i >= 0; --i) {
0145         if (emailDomain.at(i) == QLatin1Char('.')) {
0146             dotNumber++;
0147             if (dotNumber == 2) {
0148                 return emailDomain.right(emailDomain.length() - i - 1);
0149             }
0150         }
0151     }
0152     return emailDomain;
0153 }
0154 
0155 QString MessageViewer::DKIMUtil::defaultConfigFileName()
0156 {
0157     return QStringLiteral("dkimsettingsrc");
0158 }
0159 
0160 QString MessageViewer::DKIMUtil::convertAuthenticationMethodEnumToString(MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod method)
0161 {
0162     QString methodStr;
0163     switch (method) {
0164     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Unknown:
0165         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Undefined type";
0166         break;
0167     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dkim:
0168         methodStr = QStringLiteral("dkim");
0169         break;
0170     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Spf:
0171         methodStr = QStringLiteral("spf");
0172         break;
0173     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dmarc:
0174         methodStr = QStringLiteral("dmarc");
0175         break;
0176     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dkimatps:
0177         methodStr = QStringLiteral("dkim-atps");
0178         break;
0179     case MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Auth:
0180         methodStr = QStringLiteral("auth");
0181         break;
0182     }
0183     return methodStr;
0184 }
0185 
0186 MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod MessageViewer::DKIMUtil::convertAuthenticationMethodStringToEnum(const QString &str)
0187 {
0188     if (str == QLatin1StringView("dkim")) {
0189         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dkim;
0190     } else if (str == QLatin1StringView("spf")) {
0191         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Spf;
0192     } else if (str == QLatin1StringView("dmarc")) {
0193         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dmarc;
0194     } else if (str == QLatin1StringView("dkim-atps")) {
0195         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Dkimatps;
0196     } else if (str == QLatin1StringView("auth")) {
0197         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Auth;
0198     } else {
0199         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Undefined type " << str;
0200         return MessageViewer::DKIMCheckSignatureJob::AuthenticationMethod::Unknown;
0201     }
0202 }