Messagelib

dkimheaderparser.cpp
1/*
2 SPDX-FileCopyrightText: 2019-2024 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
const char * constData() const const
QByteArray fromRawData(const char *data, qsizetype size)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
qsizetype length() const const
void reserve(qsizetype size)
qsizetype size() const const
bool isSpace() const const
const_reference at(qsizetype i) const const
void clear()
qsizetype count() const const
T takeAt(qsizetype i)
QString fromLatin1(QByteArrayView str)
QString toLower() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.