Messagelib

dkimauthenticationstatusinfo.cpp
1/*
2 SPDX-FileCopyrightText: 2018-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "dkimauthenticationstatusinfo.h"
8#include "dkimauthenticationstatusinfoutil.h"
9#include "messageviewer_dkimcheckerdebug.h"
10
11#include <QRegularExpressionMatch>
12using namespace MessageViewer;
13// see https://tools.ietf.org/html/rfc7601
14DKIMAuthenticationStatusInfo::DKIMAuthenticationStatusInfo() = default;
15
16bool DKIMAuthenticationStatusInfo::parseAuthenticationStatus(const QString &key, bool relaxingParsing)
17{
18 QString valueKey = key;
19 // kmime remove extra \r\n but we need it for regexp at the end.
20 if (!valueKey.endsWith(QLatin1StringView("\r\n"))) {
21 valueKey += QLatin1StringView("\r\n");
22 }
23 // https://tools.ietf.org/html/rfc7601#section-2.2
24 // authres-header = "Authentication-Results:" [CFWS] authserv-id
25 // [ CFWS authres-version ]
26 // ( no-result / 1*resinfo ) [CFWS] CRLF
27
28 // 1) extract AuthservId and AuthVersion
30 const QString regStr = DKIMAuthenticationStatusInfoUtil::value_cp() + QLatin1StringView("(?:") + DKIMAuthenticationStatusInfoUtil::cfws_p()
31 + QLatin1StringView("([0-9]+)") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView(" )?");
32 // qDebug() << " regStr" << regStr;
33 static const QRegularExpression regular1(regStr);
34 int index = valueKey.indexOf(regular1, 0, &match);
35 if (index != -1) {
36 mAuthservId = match.captured(1);
37 const QString authVersionStr = match.captured(2);
38 if (!authVersionStr.isEmpty()) {
39 mAuthVersion = authVersionStr.toInt();
40 } else {
41 mAuthVersion = 1;
42 }
43 valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0)));
44 // qDebug() << " match.captured(0)"<<match.captured(0)<<"match.captured(1)" <<match.captured(1) << authVersionStr;
45 // qDebug() << " valueKey" << valueKey;
46 } else {
47 return false;
48 }
49 // check if message authentication was performed
50 const QString authResultStr = DKIMAuthenticationStatusInfoUtil::regexMatchO(DKIMAuthenticationStatusInfoUtil::value_cp() + QLatin1StringView(";")
51 + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView("?none"));
52 // qDebug() << "authResultStr "<<authResultStr;
53 static const QRegularExpression regular2(authResultStr);
54 index = valueKey.indexOf(regular2, 0, &match);
55 if (index != -1) {
56 // no result
57 return false;
58 }
59 while (!valueKey.isEmpty()) {
60 // qDebug() << "valueKey LOOP" << valueKey;
61 const AuthStatusInfo resultInfo = parseAuthResultInfo(valueKey, relaxingParsing);
62 if (resultInfo.isValid()) {
63 mListAuthStatusInfo.append(resultInfo);
64 }
65 }
66 return true;
67}
68
69DKIMAuthenticationStatusInfo::AuthStatusInfo DKIMAuthenticationStatusInfo::parseAuthResultInfo(QString &valueKey, bool relaxingParsing)
70{
71 DKIMAuthenticationStatusInfo::AuthStatusInfo authStatusInfo;
72 // 2) extract methodspec
73 const QString methodVersionp =
74 DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('/') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1StringView("([0-9]+)");
75 const QString method_p =
76 QLatin1Char('(') + DKIMAuthenticationStatusInfoUtil::keyword_p() + QLatin1StringView(")(?:") + methodVersionp + QLatin1StringView(")?");
77 QString Keyword_result_p = QStringLiteral("none|pass|fail|softfail|policy|neutral|temperror|permerror");
78 // older SPF specs (e.g. RFC 4408) use mixed case
79 Keyword_result_p += QLatin1StringView("|None|Pass|Fail|SoftFail|Neutral|TempError|PermError");
80 const QString result_p = QLatin1Char('=') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(') + Keyword_result_p + QLatin1Char(')');
81 const QString methodspec_p =
82 QLatin1Char(';') + DKIMAuthenticationStatusInfoUtil::cfws_op() + method_p + DKIMAuthenticationStatusInfoUtil::cfws_op() + result_p;
83
84 // qDebug() << "methodspec_p " << methodspec_p;
86 static const QRegularExpression reg2(methodspec_p);
87 int index = valueKey.indexOf(reg2, 0, &match);
88 if (index == -1) {
89 valueKey = QString(); // remove it !
90 qCDebug(MESSAGEVIEWER_DKIMCHECKER_LOG) << "methodspec not found ";
91 // no result
92 return authStatusInfo;
93 }
94 // qDebug() << " match" << match.captured(0) << match.captured(1) << match.capturedTexts();
95 authStatusInfo.method = match.captured(1);
96 const QString authVersionStr = match.captured(2);
97 if (!authVersionStr.isEmpty()) {
98 authStatusInfo.methodVersion = authVersionStr.toInt();
99 } else {
100 authStatusInfo.methodVersion = 1;
101 }
102 authStatusInfo.result = match.captured(3);
103
104 valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
105
106 // 3) extract reasonspec (optional)
107 const QString reasonspec_p =
108 DKIMAuthenticationStatusInfoUtil::regexMatchO(QLatin1StringView("reason") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('=')
109 + DKIMAuthenticationStatusInfoUtil::cfws_op() + DKIMAuthenticationStatusInfoUtil::value_cp());
110 static const QRegularExpression reg31(reasonspec_p);
111 index = valueKey.indexOf(reg31, 0, &match);
112 if (index != -1) {
113 // qDebug() << " reason " << match.capturedTexts();
114 authStatusInfo.reason = match.captured(2);
115 valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
116 }
117 // 4) extract propspec (optional)
118 QString pvalue_p = DKIMAuthenticationStatusInfoUtil::value_p() + QLatin1StringView("|(?:(?:") + DKIMAuthenticationStatusInfoUtil::localPart_p()
119 + QLatin1StringView("?@)?") + DKIMAuthenticationStatusInfoUtil::domainName_p() + QLatin1Char(')');
120 if (relaxingParsing) {
121 // Allow "/" in the header.b (or other) property, even if it is not in a quoted-string
122 pvalue_p += QStringLiteral("|[^ \\x00-\\x1F\\x7F()<>@,;:\\\\\"[\\]?=]+");
123 }
124
125 const QString property_p = QLatin1StringView("mailfrom|rcptto") + QLatin1Char('|') + DKIMAuthenticationStatusInfoUtil::keyword_p();
126 const QString propspec_p = QLatin1Char('(') + DKIMAuthenticationStatusInfoUtil::keyword_p() + QLatin1Char(')') + DKIMAuthenticationStatusInfoUtil::cfws_op()
127 + QLatin1StringView("\\.") + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(') + property_p + QLatin1Char(')')
128 + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('=') + DKIMAuthenticationStatusInfoUtil::cfws_op() + QLatin1Char('(')
129 + pvalue_p /*+ QLatin1Char(')')*/;
130
131 // qDebug() << "propspec_p " << propspec_p;
132
133 const QString regexp = DKIMAuthenticationStatusInfoUtil::regexMatchO(propspec_p);
134 static const QRegularExpression reg(regexp);
135 if (!reg.isValid()) {
136 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " reg error : " << reg.errorString();
137 } else {
138 index = valueKey.indexOf(reg, 0, &match);
139 while (index != -1) {
140 // qDebug() << " propspec " << match.capturedTexts();
141 valueKey = valueKey.right(valueKey.length() - (index + match.capturedLength(0))); // Improve it!
142 // qDebug() << " value KEy " << valueKey;
143 const QString &captured1 = match.captured(1);
144 // qDebug() << " captured1 " << captured1;
145 if (captured1 == QLatin1StringView("header")) {
146 AuthStatusInfo::Property prop;
147 prop.type = match.captured(2);
148 prop.value = match.captured(3);
149 authStatusInfo.header.append(prop);
150 } else if (captured1 == QLatin1StringView("smtp")) {
151 AuthStatusInfo::Property prop;
152 prop.type = match.captured(2);
153 prop.value = match.captured(3);
154 authStatusInfo.smtp.append(prop);
155 } else if (captured1 == QLatin1StringView("body")) {
156 AuthStatusInfo::Property prop;
157 prop.type = match.captured(2);
158 prop.value = match.captured(3);
159 authStatusInfo.body.append(prop);
160 } else if (captured1 == QLatin1StringView("policy")) {
161 AuthStatusInfo::Property prop;
162 prop.type = match.captured(2);
163 prop.value = match.captured(3);
164 authStatusInfo.policy.append(prop);
165 } else {
166 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Unknown type found " << captured1;
167 }
168 index = valueKey.indexOf(reg, 0, &match);
169 }
170 }
171 return authStatusInfo;
172}
173
174int DKIMAuthenticationStatusInfo::authVersion() const
175{
176 return mAuthVersion;
177}
178
179void DKIMAuthenticationStatusInfo::setAuthVersion(int authVersion)
180{
181 mAuthVersion = authVersion;
182}
183
184QString DKIMAuthenticationStatusInfo::reasonSpec() const
185{
186 return mReasonSpec;
187}
188
189void DKIMAuthenticationStatusInfo::setReasonSpec(const QString &reasonSpec)
190{
191 mReasonSpec = reasonSpec;
192}
193
194bool DKIMAuthenticationStatusInfo::operator==(const DKIMAuthenticationStatusInfo &other) const
195{
196 return mAuthservId == other.authservId() && mAuthVersion == other.authVersion() && mReasonSpec == other.reasonSpec()
197 && mListAuthStatusInfo == other.listAuthStatusInfo();
198}
199
200QList<DKIMAuthenticationStatusInfo::AuthStatusInfo> DKIMAuthenticationStatusInfo::listAuthStatusInfo() const
201{
202 return mListAuthStatusInfo;
203}
204
205void DKIMAuthenticationStatusInfo::setListAuthStatusInfo(const QList<AuthStatusInfo> &listAuthStatusInfo)
206{
207 mListAuthStatusInfo = listAuthStatusInfo;
208}
209
210QString DKIMAuthenticationStatusInfo::authservId() const
211{
212 return mAuthservId;
213}
214
215void DKIMAuthenticationStatusInfo::setAuthservId(const QString &authservId)
216{
217 mAuthservId = authservId;
218}
219
220QDebug operator<<(QDebug d, const DKIMAuthenticationStatusInfo &t)
221{
222 d << "mAuthservId: " << t.authservId();
223 d << "mReasonSpec: " << t.reasonSpec();
224 d << "mAuthVersion: " << t.authVersion() << '\n';
225 const auto listAuthStatusInfo = t.listAuthStatusInfo();
226 for (const DKIMAuthenticationStatusInfo::AuthStatusInfo &info : listAuthStatusInfo) {
227 d << "mListAuthStatusInfo: " << info.method << " : " << info.result << " : " << info.methodVersion << " : " << info.reason << '\n';
228 d << "Property:" << '\n';
229 if (!info.smtp.isEmpty()) {
230 for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.smtp) {
231 d << " smtp " << prop.type << " : " << prop.value << '\n';
232 }
233 }
234 if (!info.header.isEmpty()) {
235 for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.header) {
236 d << " header " << prop.type << " : " << prop.value << '\n';
237 }
238 }
239 if (!info.body.isEmpty()) {
240 for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.body) {
241 d << " body " << prop.type << " : " << prop.value << '\n';
242 }
243 }
244 if (!info.policy.isEmpty()) {
245 for (const DKIMAuthenticationStatusInfo::AuthStatusInfo::Property &prop : info.policy) {
246 d << " policy " << prop.type << " : " << prop.value << '\n';
247 }
248 }
249 }
250 return d;
251}
252
253bool DKIMAuthenticationStatusInfo::AuthStatusInfo::operator==(const DKIMAuthenticationStatusInfo::AuthStatusInfo &other) const
254{
255 return other.method == method && other.result == result && other.methodVersion == methodVersion && other.reason == reason && other.policy == policy
256 && other.smtp == smtp && other.header == header && other.body == body;
257}
258
259bool DKIMAuthenticationStatusInfo::AuthStatusInfo::isValid() const
260{
261 return !method.isEmpty();
262}
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
void append(const T &value)
bool isEmpty() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int length() const const
QString right(int n) const const
int toInt(bool *ok, int base) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.