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

KDE's Doxygen guidelines are available online.