Messagelib

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

KDE's Doxygen guidelines are available online.