Messagelib

dkimheaderparser.cpp
1/*
2 SPDX-FileCopyrightText: 2019-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "dkimheaderparser.h"
8#include <QChar>
9
10using namespace MessageViewer;
11
12DKIMHeaderParser::DKIMHeaderParser() = default;
13
14DKIMHeaderParser::~DKIMHeaderParser()
15{
16 mListHeaders.clear();
17}
18
19void DKIMHeaderParser::parse()
20{
21 mWasAlreadyParsed = true;
22 if (mHead.isEmpty()) {
23 return;
24 }
25 int cursor = 0;
26 while (cursor < mHead.size()) {
27 const int headerStart = cursor;
28 int endOfFieldBody;
29 const MessageViewer::DKIMHeaderParser::Header header = extractHeader(mHead, headerStart, endOfFieldBody);
30 if (header.isValid()) {
31 mListHeaders << header;
32 cursor = endOfFieldBody + 1;
33 } else {
34 break;
35 }
36 }
37}
38
39MessageViewer::DKIMHeaderParser::Header DKIMHeaderParser::extractHeader(const QByteArray &head, const int headerStart, int &endOfFieldBody)
40{
41 int startOfFieldBody = head.indexOf(':', headerStart);
42 if (startOfFieldBody < 0) {
43 return {};
44 }
45
46 MessageViewer::DKIMHeaderParser::Header header;
47 const char *rawType = head.constData() + headerStart;
48 const size_t rawTypeLen = startOfFieldBody - headerStart;
49
50 startOfFieldBody++; // skip the ':'
51 if (startOfFieldBody < head.size() - 1 && head[startOfFieldBody] == ' ') { // skip the space after the ':', if there's any
52 startOfFieldBody++;
53 }
54
55 bool folded = false;
56 endOfFieldBody = findHeaderLineEnd(head, startOfFieldBody, &folded);
57
58 // Store it as lowercase
59 header.headerName = QString::fromLatin1(QByteArray::fromRawData(rawType, rawTypeLen)).toLower();
60 if (folded) {
61 const auto unfoldedBody = unfoldHeader(head.constData() + startOfFieldBody, endOfFieldBody - startOfFieldBody);
62 // qDebug() << " unfoldedBody" << unfoldedBody;
63 header.headerValue = QString::fromLatin1(unfoldedBody);
64 } else {
65 const QByteArray ba = QByteArray::fromRawData(head.constData() + startOfFieldBody, endOfFieldBody - startOfFieldBody);
66 // qDebug() << " unfoldedBody ba" << ba;
67 header.headerValue = QString::fromLatin1(ba);
68 }
69 return header;
70}
71
72QByteArray DKIMHeaderParser::unfoldHeader(const char *header, size_t headerSize)
73{
74 QByteArray result;
75 if (headerSize == 0) {
76 return result;
77 }
78
79 // unfolding skips characters so result will be at worst headerSize long
80 result.reserve(headerSize);
81
82 const char *end = header + headerSize;
83 const char *pos = header;
84 const char *foldBegin = nullptr;
85 const char *foldMid = nullptr;
86 const char *foldEnd = nullptr;
87 while ((foldMid = strchr(pos, '\n')) && foldMid < end) {
88 foldBegin = foldEnd = foldMid;
89 // find the first space before the line-break
90 while (foldBegin) {
91 if (!QChar::isSpace(*(foldBegin - 1))) {
92 break;
93 }
94 --foldBegin;
95 }
96 // find the first non-space after the line-break
97 while (foldEnd <= end - 1) {
98 if (QChar::isSpace(*foldEnd)) {
99 ++foldEnd;
100 } else if (foldEnd && *(foldEnd - 1) == '\n' && *foldEnd == '=' && foldEnd + 2 < (header + headerSize - 1)
101 && ((*(foldEnd + 1) == '0' && *(foldEnd + 2) == '9') || (*(foldEnd + 1) == '2' && *(foldEnd + 2) == '0'))) {
102 // bug #86302: malformed header continuation starting with =09/=20
103 foldEnd += 3;
104 } else {
105 break;
106 }
107 }
108
109 result.append(pos, foldBegin - pos);
110 if (foldEnd < end - 1) {
111 result += ' ';
112 }
113 pos = foldEnd;
114 }
115 if (end > pos) {
116 result.append(pos, end - pos);
117 }
118 return result;
119}
120
121QList<DKIMHeaderParser::Header> DKIMHeaderParser::listHeaders() const
122{
123 return mListHeaders;
124}
125
126bool DKIMHeaderParser::wasAlreadyParsed() const
127{
128 return mWasAlreadyParsed;
129}
130
131void DKIMHeaderParser::setWasAlreadyParsed(bool wasAlreadyParsed)
132{
133 mWasAlreadyParsed = wasAlreadyParsed;
134}
135
136bool DKIMHeaderParser::operator==(const DKIMHeaderParser &other) const
137{
138 return other.head() == mHead && other.listHeaders() == mListHeaders && other.wasAlreadyParsed() == mWasAlreadyParsed;
139}
140
141QByteArray DKIMHeaderParser::head() const
142{
143 return mHead;
144}
145
146void DKIMHeaderParser::setHead(const QByteArray &head)
147{
148 mHead = head;
149}
150
151int DKIMHeaderParser::findHeaderLineEnd(const QByteArray &src, int &dataBegin, bool *folded)
152{
153 int end = dataBegin;
154 const int len = src.length() - 1;
155
156 if (folded) {
157 *folded = false;
158 }
159
160 if (dataBegin < 0) {
161 // Not found
162 return -1;
163 }
164
165 if (dataBegin > len) {
166 // No data available
167 return len + 1;
168 }
169
170 // If the first line contains nothing, but the next line starts with a space
171 // or a tab, that means a stupid mail client has made the first header field line
172 // entirely empty, and has folded the rest to the next line(s).
173 if (src.at(end) == '\n' && end + 1 < len && (src[end + 1] == ' ' || src[end + 1] == '\t')) {
174 // Skip \n and first whitespace
175 dataBegin += 2;
176 end += 2;
177 }
178
179 if (src.at(end) != '\n') { // check if the header is not empty
180 while (true) {
181 end = src.indexOf('\n', end + 1);
182 if (end == -1 || end == len) {
183 // end of string
184 break;
185 } else if (src[end + 1] == ' ' || src[end + 1] == '\t'
186 || (src[end + 1] == '=' && end + 3 <= len
187 && ((src[end + 2] == '0' && src[end + 3] == '9') || (src[end + 2] == '2' && src[end + 3] == '0')))) {
188 // next line is header continuation or starts with =09/=20 (bug #86302)
189 if (folded) {
190 *folded = true;
191 }
192 } else {
193 // end of header (no header continuation)
194 break;
195 }
196 }
197 }
198
199 if (end < 0) {
200 end = len + 1; // take the rest of the string
201 }
202 return end;
203}
204
205QString DKIMHeaderParser::headerType(const QString &str)
206{
207 for (int i = mListHeaders.count() - 1; i >= 0; --i) {
208 if (mListHeaders.at(i).headerName == str) {
209 DKIMHeaderParser::Header header = mListHeaders.takeAt(i);
210 const QString headerValue = header.headerValue;
211 return headerValue;
212 }
213 }
214 return {};
215}
The DKIMHeaderParser class.
const QList< QKeySequence > & end()
QByteArray & append(QByteArrayView data)
char at(qsizetype i) const const
QByteArray fromRawData(const char *data, qsizetype size)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
qsizetype length() const const
void reserve(qsizetype size)
bool isSpace() const const
QString fromLatin1(QByteArrayView str)
QString toLower() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 12:05:41 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.