Messagelib

dkimheaderparser.cpp
1 /*
2  SPDX-FileCopyrightText: 2019-2023 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "dkimheaderparser.h"
8 #include <QChar>
9 
10 using namespace MessageViewer;
11 
12 DKIMHeaderParser::DKIMHeaderParser() = default;
13 
14 DKIMHeaderParser::~DKIMHeaderParser()
15 {
16  mListHeaders.clear();
17 }
18 
19 void 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 
39 MessageViewer::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 
72 QByteArray 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 
121 QVector<DKIMHeaderParser::Header> DKIMHeaderParser::listHeaders() const
122 {
123  return mListHeaders;
124 }
125 
126 bool DKIMHeaderParser::wasAlreadyParsed() const
127 {
128  return mWasAlreadyParsed;
129 }
130 
131 void DKIMHeaderParser::setWasAlreadyParsed(bool wasAlreadyParsed)
132 {
133  mWasAlreadyParsed = wasAlreadyParsed;
134 }
135 
136 bool DKIMHeaderParser::operator==(const DKIMHeaderParser &other) const
137 {
138  return other.head() == mHead && other.listHeaders() == mListHeaders && other.wasAlreadyParsed() == mWasAlreadyParsed;
139 }
140 
141 QByteArray DKIMHeaderParser::head() const
142 {
143  return mHead;
144 }
145 
146 void DKIMHeaderParser::setHead(const QByteArray &head)
147 {
148  mHead = head;
149 }
150 
151 int 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 
205 QString 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 }
T takeAt(int i)
QByteArray fromRawData(const char *data, int size)
int indexOf(char ch, int from) const const
QByteArray & append(char ch)
char at(int i) const const
void clear()
bool isSpace() const const
const T & at(int i) const const
The DKIMHeaderParser class.
QString toLower() const const
bool isEmpty() const const
const char * constData() const const
QString fromLatin1(const char *str, int size)
void reserve(int size)
int count(const T &value) const const
int size() const const
int length() const const
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Mar 27 2023 04:08:17 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.