KMime

kmime_util.cpp
1/*
2 kmime_util.cpp
3
4 KMime, the KDE Internet mail/usenet news message library.
5 SPDX-FileCopyrightText: 2001 the KMime authors.
6 See file AUTHORS for details
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "kmime_util.h"
12#include "kmime_util_p.h"
13
14#include "kmime_charfreq_p.h"
15#include "kmime_debug.h"
16#include "kmime_header_parsing.h"
17#include "kmime_message.h"
18#include "kmime_warning_p.h"
19
20#include <QCoreApplication>
21
22#include <algorithm>
23#include <cctype>
24#include <cstdlib>
25#include <ctime>
26
27using namespace KMime;
28
29namespace KMime
30{
31
32QList<QByteArray> c_harsetCache;
33
34QByteArray cachedCharset(const QByteArray &name)
35{
36 for (const QByteArray &charset : std::as_const(c_harsetCache)) {
37 if (qstricmp(name.data(), charset.data()) == 0) {
38 return charset;
39 }
40 }
41
42 c_harsetCache.append(name.toUpper());
43 //qCDebug(KMIME_LOG) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
44 return c_harsetCache.last();
45}
46
47bool isUsAscii(const QString &s)
48{
49 const uint sLength = s.length();
50 for (uint i = 0; i < sLength; i++) {
51 if (s.at(i).toLatin1() <= 0) { // c==0: non-latin1, c<0: non-us-ascii
52 return false;
53 }
54 }
55 return true;
56}
57
58QString nameForEncoding(Headers::contentEncoding enc)
59{
60 switch (enc) {
61 case Headers::CE7Bit: return QStringLiteral("7bit");
62 case Headers::CE8Bit: return QStringLiteral("8bit");
63 case Headers::CEquPr: return QStringLiteral("quoted-printable");
64 case Headers::CEbase64: return QStringLiteral("base64");
65 case Headers::CEuuenc: return QStringLiteral("uuencode");
66 case Headers::CEbinary: return QStringLiteral("binary");
67 default: return QStringLiteral("unknown");
68 }
69}
70
71QList<Headers::contentEncoding> encodingsForData(const QByteArray &data) {
73 CharFreq cf(data);
74
75 switch (cf.type()) {
76 case CharFreq::SevenBitText:
77 allowed << Headers::CE7Bit;
78 [[fallthrough]];
79 case CharFreq::EightBitText:
80 allowed << Headers::CE8Bit;
81 [[fallthrough]];
82 case CharFreq::SevenBitData:
83 if (cf.printableRatio() > 5.0 / 6.0) {
84 // let n the length of data and p the number of printable chars.
85 // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
86 // => qp < base64 iff p > 5n/6.
87 allowed << Headers::CEquPr;
88 allowed << Headers::CEbase64;
89 } else {
90 allowed << Headers::CEbase64;
91 allowed << Headers::CEquPr;
92 }
93 break;
94 case CharFreq::EightBitData:
95 allowed << Headers::CEbase64;
96 break;
97 case CharFreq::None:
98 default:
99 Q_ASSERT(false);
100 }
101
102 return allowed;
103}
104
105// all except specials, CTLs, SPACE.
106const uchar aTextMap[16] = {
107 0x00, 0x00, 0x00, 0x00,
108 0x5F, 0x35, 0xFF, 0xC5,
109 0x7F, 0xFF, 0xFF, 0xE3,
110 0xFF, 0xFF, 0xFF, 0xFE
111};
112
113// all except tspecials, CTLs, SPACE.
114const uchar tTextMap[16] = {
115 0x00, 0x00, 0x00, 0x00,
116 0x5F, 0x36, 0xFF, 0xC0,
117 0x7F, 0xFF, 0xFF, 0xE3,
118 0xFF, 0xFF, 0xFF, 0xFE
119};
120
121QByteArray uniqueString()
122{
123 static const char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
124 time_t now;
125 char p[11];
126 int ran;
127 unsigned int timeval;
128
129 p[10] = '\0';
130 now = time(nullptr);
131 ran = 1 + (int)(1000.0 * rand() / (RAND_MAX + 1.0));
132 timeval = (now / ran) + QCoreApplication::applicationPid();
133
134 for (int i = 0; i < 10; i++) {
135 int pos = (int)(61.0 * rand() / (RAND_MAX + 1.0));
136 //qCDebug(KMIME_LOG) << pos;
137 p[i] = chars[pos];
138 }
139
140 QByteArray ret;
141 ret.setNum(timeval);
142 ret += '.';
143 ret += p;
144
145 return ret;
146}
147
148QByteArray multiPartBoundary()
149{
150 return "nextPart" + uniqueString();
151}
152
153QByteArray CRLFtoLF(const QByteArray &s)
154{
155 if (!s.contains("\r\n")) {
156 return s;
157 }
158
159 QByteArray ret = s;
160 ret.replace("\r\n", "\n");
161 return ret;
162}
163
164QByteArray CRLFtoLF(const char *s)
165{
166 QByteArray ret = s;
167 return CRLFtoLF(ret);
168}
169
170QByteArray LFtoCRLF(const QByteArray &s)
171{
172 const int firstNewline = s.indexOf('\n');
173 if (firstNewline == -1) {
174 return s;
175 }
176 if (firstNewline > 0 && s.at(firstNewline - 1) == '\r') {
177 // We found \r\n already, don't change anything
178 // This check assumes that input is consistent in terms of newlines,
179 // but so did if (s.contains("\r\n")), too.
180 return s;
181 }
182
183 QByteArray ret = s;
184 ret.replace('\n', "\r\n");
185 return ret;
186}
187
188QByteArray LFtoCRLF(const char *s)
189{
190 QByteArray ret = s;
191 return LFtoCRLF(ret);
192}
193
194bool isCryptoPart(Content *content)
195{
196 auto ct = content->contentType(false);
197 if (!ct || !ct->isMediatype("application")) {
198 return false;
199 }
200
201 const QByteArray lowerSubType = ct->subType().toLower();
202 if (lowerSubType == "pgp-encrypted" ||
203 lowerSubType == "pgp-signature" ||
204 lowerSubType == "pkcs7-mime" ||
205 lowerSubType == "x-pkcs7-mime" ||
206 lowerSubType == "pkcs7-signature" ||
207 lowerSubType == "x-pkcs7-signature") {
208 return true;
209 }
210
211 if (lowerSubType == "octet-stream") {
212 auto cd = content->contentDisposition(false);
213 if (!cd) {
214 return false;
215 }
216 const auto fileName = cd->filename().toLower();
217 return fileName == QLatin1StringView("msg.asc") ||
218 fileName == QLatin1StringView("encrypted.asc");
219 }
220
221 return false;
222}
223
224bool isAttachment(Content* content)
225{
226 if (!content) {
227 return false;
228 }
229
230 const auto contentType = content->contentType(false);
231 // multipart/* is never an attachment itself, message/rfc822 always is
232 if (contentType) {
233 if (contentType->isMultipart()) {
234 return false;
235 }
236 if (contentType->isMimeType("message/rfc822")) {
237 return true;
238 }
239 }
240
241 // the main body part is not an attachment
242 if (content->parent()) {
243 const auto top = content->topLevel();
244 if (content == top->textContent()) {
245 return false;
246 }
247 }
248
249 // ignore crypto parts
250 if (isCryptoPart(content)) {
251 return false;
252 }
253
254 // content type or content disposition having a file name set looks like an attachment
255 const auto contentDisposition = content->contentDisposition(false);
256 if (contentDisposition && !contentDisposition->filename().isEmpty()) {
257 return true;
258 }
259
260 if (contentType && !contentType->name().isEmpty()) {
261 return true;
262 }
263
264 // "attachment" content disposition is otherwise a good indicator though
265 if (contentDisposition && contentDisposition->disposition() == Headers::CDattachment) {
266 return true;
267 }
268
269 return false;
270}
271
272bool hasAttachment(Content *content)
273{
274 if (!content) {
275 return false;
276 }
277
278 if (isAttachment(content)) {
279 return true;
280 }
281
282 // Ok, content itself is not an attachment. now we deal with multiparts
283 auto ct = content->contentType(false);
284 if (ct && ct->isMultipart() && !ct->isSubtype("related")) {// && !ct->isSubtype("alternative")) {
285 const auto contents = content->contents();
286 for (Content *child : contents) {
287 if (hasAttachment(child)) {
288 return true;
289 }
290 }
291 }
292 return false;
293}
294
295bool hasInvitation(Content *content)
296{
297 if (!content) {
298 return false;
299 }
300
301 if (isInvitation(content)) {
302 return true;
303 }
304
305 // Ok, content itself is not an invitation. now we deal with multiparts
306 if (content->contentType()->isMultipart()) {
307 const auto contents = content->contents();
308 for (Content *child : contents) {
309 if (hasInvitation(child)) {
310 return true;
311 }
312 }
313 }
314 return false;
315}
316
317bool isSigned(Message *message)
318{
319 if (!message) {
320 return false;
321 }
322
323 const KMime::Headers::ContentType *const contentType = message->contentType();
324 if (contentType->isSubtype("signed") ||
325 contentType->isSubtype("pgp-signature") ||
326 contentType->isSubtype("pkcs7-signature") ||
327 contentType->isSubtype("x-pkcs7-signature") ||
328 message->mainBodyPart("multipart/signed") ||
329 message->mainBodyPart("application/pgp-signature") ||
330 message->mainBodyPart("application/pkcs7-signature") ||
331 message->mainBodyPart("application/x-pkcs7-signature")) {
332 return true;
333 }
334 return false;
335}
336
337bool isEncrypted(Message *message)
338{
339 if (!message) {
340 return false;
341 }
342
343 const KMime::Headers::ContentType *const contentType = message->contentType();
344 if (contentType->isSubtype("encrypted") ||
345 contentType->isSubtype("pgp-encrypted") ||
346 contentType->isSubtype("pkcs7-mime") ||
347 contentType->isSubtype("x-pkcs7-mime") ||
348 message->mainBodyPart("multipart/encrypted") ||
349 message->mainBodyPart("application/pgp-encrypted") ||
350 message->mainBodyPart("application/pkcs7-mime") ||
351 message->mainBodyPart("application/x-pkcs7-mime")) {
352 return true;
353 }
354
355 return false;
356}
357
358bool isInvitation(Content *content)
359{
360 if (!content) {
361 return false;
362 }
363
364 const KMime::Headers::ContentType *const contentType = content->contentType(false);
365
366 if (contentType && contentType->isMediatype("text") && contentType->isSubtype("calendar")) {
367 return true;
368 }
369
370 return false;
371}
372
373} // namespace KMime
A class that encapsulates MIME encoded Content.
Headers::ContentType * contentType(bool create=true)
Returns the Content-Type header.
Content * topLevel() const
Returns the toplevel content object, 0 if there is no such object.
Content * parent() const
Returns the parent content object, or 0 if the content doesn't have a parent.
Headers::ContentDisposition * contentDisposition(bool create=true)
Returns the Content-Disposition header.
QList< Content * > contents() const
For multipart contents, this will return a list of all multipart child contents.
QString filename() const
Returns the suggested filename for the associated MIME part.
Represents a "Content-Type" header.
bool isMediatype(const char *mediatype) const
Tests if the media type equals mediatype.
bool isSubtype(const char *subtype) const
Tests if the mime sub-type equals subtype.
bool isMultipart() const
Returns true if the associated MIME entity is a multipart container.
Represents a (email) message.
Content * mainBodyPart(const QByteArray &type=QByteArray())
Returns the first main body part of a given type, taking multipart/mixed and multipart/alternative no...
contentEncoding
Various possible values for the "Content-Transfer-Encoding" header.
contentDisposition
Various possible values for the "Content-Disposition" header.
QString name(StandardShortcut id)
char at(qsizetype i) const const
bool contains(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QByteArray & setNum(double n, char format, int precision)
QByteArray toLower() const const
char toLatin1() const const
qint64 applicationPid()
void append(QList< T > &&value)
T & last()
const QChar at(qsizetype position) const const
QChar * data()
qsizetype length() const const
QString toLower() const const
QString toUpper() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.