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 }