/src/kcodecs/src/kcodecs.cpp
Line | Count | Source |
1 | | /* |
2 | | SPDX-FileCopyrightText: 2000-2001 Dawit Alemayehu <adawit@kde.org> |
3 | | SPDX-FileCopyrightText: 2001 Rik Hemsley (rikkus) <rik@kde.org> |
4 | | SPDX-FileCopyrightText: 2001-2002 Marc Mutz <mutz@kde.org> |
5 | | |
6 | | SPDX-License-Identifier: LGPL-2.0-only |
7 | | |
8 | | The encoding and decoding utilities in KCodecs with the exception of |
9 | | quoted-printable are based on the java implementation in HTTPClient |
10 | | package by Ronald Tschalär Copyright (C) 1996-1999. // krazy:exclude=copyright |
11 | | |
12 | | The quoted-printable codec as described in RFC 2045, section 6.7. is by |
13 | | Rik Hemsley (C) 2001. |
14 | | */ |
15 | | |
16 | | #include "kcodecs.h" |
17 | | #include "kcharsets.h" |
18 | | #include "kcodecs_debug.h" |
19 | | #include "kcodecs_p.h" |
20 | | #include "kcodecsbase64.h" |
21 | | #include "kcodecsqp.h" |
22 | | #include "kcodecsuuencode.h" |
23 | | |
24 | | #include <array> |
25 | | #include <cassert> |
26 | | #include <cstring> |
27 | | #include <stdlib.h> |
28 | | #include <string.h> |
29 | | |
30 | | #include <QDebug> |
31 | | #include <QStringDecoder> |
32 | | #include <QStringEncoder> |
33 | | |
34 | | namespace KCodecs |
35 | | { |
36 | | static QList<QByteArray> charsetCache; |
37 | | |
38 | | QByteArray cachedCharset(const QByteArray &name) |
39 | 24.9k | { |
40 | 91.4k | auto it = std::find_if(charsetCache.cbegin(), charsetCache.cend(), [&name](const QByteArray &charset) { |
41 | 91.4k | return name.compare(charset, Qt::CaseInsensitive) == 0; |
42 | 91.4k | }); |
43 | 24.9k | if (it != charsetCache.cend()) { |
44 | 24.9k | return *it; |
45 | 24.9k | } |
46 | | |
47 | 2 | charsetCache.append(name.toUpper()); |
48 | 2 | return charsetCache.last(); |
49 | 24.9k | } |
50 | | |
51 | | QByteArray cachedCharset(QByteArrayView name) |
52 | 1.20M | { |
53 | 1.49M | auto it = std::find_if(charsetCache.cbegin(), charsetCache.cend(), [&name](const QByteArray &charset) { |
54 | 1.49M | return name.compare(charset, Qt::CaseInsensitive) == 0; |
55 | 1.49M | }); |
56 | 1.20M | if (it != charsetCache.cend()) { |
57 | 1.20M | return *it; |
58 | 1.20M | } |
59 | | |
60 | 64 | charsetCache.append(name.toByteArray().toUpper()); |
61 | 64 | return charsetCache.last(); |
62 | 1.20M | } |
63 | | |
64 | | namespace CodecNames |
65 | | { |
66 | | QByteArray utf8() |
67 | 12.2k | { |
68 | 12.2k | return QByteArrayLiteral("UTF-8"); |
69 | 12.2k | } |
70 | | } |
71 | | |
72 | | Q_REQUIRED_RESULT |
73 | | QByteArray updateEncodingCharset(const QByteArray ¤tCharset, const QByteArray &nextCharset) |
74 | 1.22M | { |
75 | 1.22M | if (!nextCharset.isEmpty()) { |
76 | 1.22M | if (currentCharset.isEmpty()) { |
77 | 1.19M | return nextCharset; |
78 | 1.19M | } |
79 | 28.9k | if (currentCharset != nextCharset) { |
80 | | // only one charset per string supported, so change to superset charset UTF-8, |
81 | | // which should cover any possible chars |
82 | 11.9k | return CodecNames::utf8(); |
83 | 11.9k | } |
84 | 28.9k | } |
85 | 19.9k | return currentCharset; |
86 | 1.22M | } |
87 | | |
88 | | } // namespace KCodecs |
89 | | |
90 | | /******************************** KCodecs ********************************/ |
91 | | |
92 | | QByteArray KCodecs::quotedPrintableEncode(QByteArrayView in, bool useCRLF) |
93 | 0 | { |
94 | 0 | Codec *codec = Codec::codecForName("quoted-printable"); |
95 | 0 | return codec->encode(in, useCRLF ? Codec::NewlineCRLF : Codec::NewlineLF); |
96 | 0 | } |
97 | | |
98 | | void KCodecs::quotedPrintableEncode(QByteArrayView in, QByteArray &out, bool useCRLF) |
99 | 0 | { |
100 | 0 | out = quotedPrintableEncode(in, useCRLF ? Codec::NewlineCRLF : Codec::NewlineLF); |
101 | 0 | } |
102 | | |
103 | | QByteArray KCodecs::quotedPrintableDecode(QByteArrayView in) |
104 | 9.44k | { |
105 | 9.44k | Codec *codec = Codec::codecForName("quoted-printable"); |
106 | 9.44k | return codec->decode(in); |
107 | 9.44k | } |
108 | | |
109 | | void KCodecs::quotedPrintableDecode(QByteArrayView in, QByteArray &out) |
110 | 0 | { |
111 | 0 | out = quotedPrintableDecode(in); |
112 | 0 | } |
113 | | |
114 | | QByteArray KCodecs::base64Encode(QByteArrayView in) |
115 | 125k | { |
116 | 125k | Codec *codec = Codec::codecForName("base64"); |
117 | 125k | return codec->encode(in); |
118 | 125k | } |
119 | | |
120 | | void KCodecs::base64Encode(QByteArrayView in, QByteArray &out, bool insertLFs) |
121 | 125k | { |
122 | 125k | Q_UNUSED(insertLFs); |
123 | 125k | out = base64Encode(in); |
124 | 125k | } |
125 | | |
126 | | QByteArray KCodecs::base64Decode(QByteArrayView in) |
127 | 1.48k | { |
128 | 1.48k | Codec *codec = Codec::codecForName("base64"); |
129 | 1.48k | return codec->decode(in); |
130 | 1.48k | } |
131 | | |
132 | | void KCodecs::base64Decode(const QByteArrayView in, QByteArray &out) |
133 | 0 | { |
134 | 0 | out = base64Decode(in); |
135 | 0 | } |
136 | | |
137 | | QByteArray KCodecs::uudecode(QByteArrayView in) |
138 | 127k | { |
139 | 127k | Codec *codec = Codec::codecForName("x-uuencode"); |
140 | 127k | return codec->decode(in); |
141 | 127k | } |
142 | | |
143 | | void KCodecs::uudecode(QByteArrayView in, QByteArray &out) |
144 | 120k | { |
145 | 120k | out = uudecode(in); |
146 | 120k | } |
147 | | |
148 | | //@cond PRIVATE |
149 | | |
150 | | namespace KCodecs |
151 | | { |
152 | | // parse the encoded-word (scursor points to after the initial '=') |
153 | | // retry point is the next position at which a failed parsing of an encoded word |
154 | | // should be retried from. This is an optimization to avoid quadratic behavior on |
155 | | // (intentionally) corrupted input |
156 | | bool parseEncodedWord(const char *&scursor, |
157 | | const char *const send, |
158 | | QString *result, |
159 | | QByteArray *usedCS, |
160 | | const QByteArray &defaultCS, |
161 | | CharsetOption charsetOption, |
162 | | const char *&retryPoint) |
163 | 306k | { |
164 | 306k | assert(result); |
165 | | |
166 | | // make sure the caller already did a bit of the work. |
167 | 306k | assert(*(scursor - 1) == '='); |
168 | | |
169 | | // |
170 | | // STEP 1: |
171 | | // scan for the charset/language portion of the encoded-word |
172 | | // |
173 | | |
174 | 306k | retryPoint = nullptr; |
175 | 306k | char ch = *scursor++; |
176 | | |
177 | 306k | if (ch != '?') { |
178 | | // qCDebug(KCODECS_LOG) << "first"; |
179 | | // qCDebug(KCODECS_LOG) << "Premature end of encoded word"; |
180 | 206k | return false; |
181 | 206k | } |
182 | | |
183 | | // remember start of charset (i.e. just after the initial "=?") and |
184 | | // language (just after the first '*') fields: |
185 | 99.7k | const char *charsetStart = scursor; |
186 | 99.7k | const char *languageStart = nullptr; |
187 | | |
188 | | // find delimiting '?' (and the '*' separating charset and language |
189 | | // tags, if any): |
190 | 2.10M | for (; scursor != send; scursor++) { |
191 | 2.09M | if (*scursor == '?') { |
192 | 86.9k | break; |
193 | 2.00M | } else if (*scursor == '*' && languageStart == nullptr) { |
194 | 2.42k | languageStart = scursor + 1; |
195 | 2.42k | } |
196 | 2.09M | } |
197 | | |
198 | | // not found? can't be an encoded-word! |
199 | 99.7k | if (scursor == send || *scursor != '?') { |
200 | | // qCDebug(KCODECS_LOG) << "second"; |
201 | | // qCDebug(KCODECS_LOG) << "Premature end of encoded word"; |
202 | 12.8k | return false; |
203 | 12.8k | } |
204 | | |
205 | | // extract charset information (keep in mind: the size given to the |
206 | | // ctor is one off due to the \0 terminator): |
207 | 86.9k | QByteArrayView maybeCharset(charsetStart, std::min<qsizetype>((languageStart ? languageStart - 1 : scursor) - charsetStart, std::strlen(charsetStart))); |
208 | | |
209 | | // |
210 | | // STEP 2: |
211 | | // scan for the encoding portion of the encoded-word |
212 | | // |
213 | | |
214 | | // remember start of encoding (just _after_ the second '?'): |
215 | 86.9k | scursor++; |
216 | 86.9k | const char *encodingStart = scursor; |
217 | | |
218 | | // find next '?' (ending the encoding tag): |
219 | 14.6M | for (; scursor != send; scursor++) { |
220 | 14.6M | if (*scursor == '?') { |
221 | 81.8k | break; |
222 | 81.8k | } |
223 | 14.6M | } |
224 | | |
225 | | // not found? Can't be an encoded-word! |
226 | 86.9k | if (scursor == send || *scursor != '?') { |
227 | | // qCDebug(KCODECS_LOG) << "third"; |
228 | | // qCDebug(KCODECS_LOG) << "Premature end of encoded word"; |
229 | 5.10k | return false; |
230 | 5.10k | } |
231 | | |
232 | | // extract the encoding information: |
233 | 81.8k | QByteArrayView maybeEncoding(encodingStart, scursor - encodingStart); |
234 | | |
235 | | // qCDebug(KCODECS_LOG) << "parseEncodedWord: found charset == \"" << maybeCharset |
236 | | // << "\"; language == \"" << maybeLanguage |
237 | | // << "\"; encoding == \"" << maybeEncoding << "\""; |
238 | | |
239 | | // |
240 | | // STEP 3: |
241 | | // scan for encoded-text portion of encoded-word |
242 | | // |
243 | | |
244 | | // remember start of encoded-text (just after the third '?'): |
245 | 81.8k | scursor++; |
246 | 81.8k | const char *encodedTextStart = scursor; |
247 | | |
248 | | // find the '?=' sequence (ending the encoded-text): |
249 | 83.5M | for (; scursor != send; scursor++) { |
250 | 83.5M | if (*scursor == '?') { |
251 | 10.5M | if (scursor + 1 != send) { |
252 | 10.5M | if (*(scursor + 1) != '=') { // We expect a '=' after the '?', but we got something else; ignore |
253 | | // qCDebug(KCODECS_LOG) << "Stray '?' in q-encoded word, ignoring this."; |
254 | 10.4M | if (*(scursor - 1) == '=') { |
255 | 55.5k | retryPoint = scursor - 1; |
256 | 55.5k | } |
257 | 10.4M | continue; |
258 | 10.4M | } else { // yep, found a '?=' sequence |
259 | 69.7k | scursor += 2; |
260 | 69.7k | break; |
261 | 69.7k | } |
262 | 10.5M | } else { // The '?' is the last char, but we need a '=' after it! |
263 | | // qCDebug(KCODECS_LOG) << "Premature end of encoded word"; |
264 | 1.33k | return false; |
265 | 1.33k | } |
266 | 10.5M | } |
267 | 83.5M | } |
268 | | |
269 | 80.5k | if (*(scursor - 2) != '?' || *(scursor - 1) != '=' || scursor < encodedTextStart + 2) { |
270 | | // qCDebug(KCODECS_LOG) << "Premature end of encoded word"; |
271 | 10.7k | if (!retryPoint) { |
272 | 8.64k | retryPoint = scursor; |
273 | 8.64k | } |
274 | 10.7k | return false; |
275 | 10.7k | } |
276 | | |
277 | | // set end sentinel for encoded-text: |
278 | 69.7k | const char *const encodedTextEnd = scursor - 2; |
279 | | |
280 | | // |
281 | | // STEP 4: |
282 | | // setup decoders for the transfer encoding and the charset |
283 | | // |
284 | | |
285 | | // try if there's a codec for the encoding found: |
286 | 69.7k | Codec *codec = Codec::codecForName(maybeEncoding); |
287 | 69.7k | if (!codec) { |
288 | | // qCDebug(KCODECS_LOG) << "Unknown encoding" << maybeEncoding; |
289 | 22.0k | return false; |
290 | 22.0k | } |
291 | | |
292 | | // get an instance of a corresponding decoder: |
293 | 47.7k | Decoder *dec = codec->makeDecoder(); |
294 | 47.7k | assert(dec); |
295 | | |
296 | | // try if there's a (text)codec for the charset found: |
297 | 47.7k | QByteArray cs; |
298 | 47.7k | QStringDecoder textCodec; |
299 | 47.7k | if (charsetOption == KCodecs::ForceDefaultCharset || maybeCharset.isEmpty()) { |
300 | 13.2k | textCodec = QStringDecoder(defaultCS); |
301 | 13.2k | cs = cachedCharset(defaultCS); |
302 | 34.4k | } else { |
303 | 34.4k | textCodec = QStringDecoder(maybeCharset); |
304 | 34.4k | if (!textCodec.isValid()) { // no suitable codec found => use default charset |
305 | 11.6k | textCodec = QStringDecoder(defaultCS); |
306 | 11.6k | cs = cachedCharset(defaultCS); |
307 | 22.8k | } else { |
308 | 22.8k | cs = cachedCharset(maybeCharset); |
309 | 22.8k | } |
310 | 34.4k | } |
311 | 47.7k | if (usedCS) { |
312 | 47.7k | *usedCS = updateEncodingCharset(*usedCS, cs); |
313 | 47.7k | } |
314 | | |
315 | 47.7k | if (!textCodec.isValid()) { |
316 | | // qCDebug(KCODECS_LOG) << "Unknown charset" << maybeCharset; |
317 | 2.97k | delete dec; |
318 | 2.97k | return false; |
319 | 44.7k | }; |
320 | | |
321 | | // qCDebug(KCODECS_LOG) << "mimeName(): \"" << textCodec->name() << "\""; |
322 | | |
323 | | // allocate a temporary buffer to store the 8bit text: |
324 | 44.7k | int encodedTextLength = encodedTextEnd - encodedTextStart; |
325 | 44.7k | QByteArray buffer; |
326 | 44.7k | buffer.resize(codec->maxDecodedSizeFor(encodedTextLength)); |
327 | 44.7k | char *bbegin = buffer.data(); |
328 | 44.7k | char *bend = bbegin + buffer.length(); |
329 | | |
330 | | // |
331 | | // STEP 5: |
332 | | // do the actual decoding |
333 | | // |
334 | | |
335 | 44.7k | if (!dec->decode(encodedTextStart, encodedTextEnd, bbegin, bend)) { |
336 | 8.01k | qWarning() << codec->name() << "codec lies about its maxDecodedSizeFor(" << encodedTextLength << ")\nresult may be truncated"; |
337 | 8.01k | } |
338 | | |
339 | 44.7k | *result = textCodec.decode(QByteArrayView(buffer.data(), bbegin - buffer.data())); |
340 | | |
341 | | // qCDebug(KCODECS_LOG) << "result now: \"" << result << "\""; |
342 | | // cleanup: |
343 | 44.7k | delete dec; |
344 | | |
345 | 44.7k | return true; |
346 | 47.7k | } |
347 | | |
348 | | } // namespace KCodecs |
349 | | |
350 | | //@endcond |
351 | | |
352 | | QString KCodecs::decodeRFC2047String(QStringView msg) |
353 | 0 | { |
354 | 0 | QByteArray usedCS; |
355 | 0 | return decodeRFC2047String(msg.toUtf8(), &usedCS, CodecNames::utf8(), NoOption); |
356 | 0 | } |
357 | | |
358 | | QString KCodecs::decodeRFC2047String(QByteArrayView src, QByteArray *usedCS, const QByteArray &defaultCS, CharsetOption charsetOption) |
359 | 1.49M | { |
360 | 1.49M | QByteArray result; |
361 | 1.49M | QByteArray spaceBuffer; |
362 | 1.49M | const char *scursor = src.constData(); |
363 | 1.49M | const char *send = scursor + src.length(); |
364 | 1.49M | bool onlySpacesSinceLastWord = false; |
365 | 1.49M | if (usedCS) { |
366 | 1.49M | usedCS->clear(); |
367 | 1.49M | } |
368 | | |
369 | 1.49M | const char *retryPoint = nullptr; |
370 | 120M | while (scursor != send) { |
371 | | // space |
372 | 119M | if (isspace(*scursor) && onlySpacesSinceLastWord) { |
373 | 8.72k | spaceBuffer += *scursor++; |
374 | 8.72k | continue; |
375 | 8.72k | } |
376 | | |
377 | | // possible start of an encoded word |
378 | 119M | if (*scursor == '=' && (!retryPoint || retryPoint <= scursor)) { |
379 | 306k | QString decoded; |
380 | 306k | ++scursor; |
381 | 306k | const char *start = scursor; |
382 | 306k | if (parseEncodedWord(scursor, send, &decoded, usedCS, defaultCS, charsetOption, retryPoint)) { |
383 | 44.7k | result += decoded.toUtf8(); |
384 | 44.7k | onlySpacesSinceLastWord = true; |
385 | 44.7k | spaceBuffer.clear(); |
386 | 261k | } else { |
387 | 261k | if (onlySpacesSinceLastWord) { |
388 | 3.67k | result += spaceBuffer; |
389 | 3.67k | onlySpacesSinceLastWord = false; |
390 | 3.67k | } |
391 | 261k | result += '='; |
392 | 261k | scursor = start; // reset cursor after parsing failure |
393 | 261k | } |
394 | 306k | continue; |
395 | 119M | } else { |
396 | | // unencoded data |
397 | 119M | if (onlySpacesSinceLastWord) { |
398 | 29.1k | result += spaceBuffer; |
399 | 29.1k | onlySpacesSinceLastWord = false; |
400 | 29.1k | } |
401 | 119M | result += *scursor; |
402 | 119M | ++scursor; |
403 | 119M | } |
404 | 119M | } |
405 | | // If there are any chars that couldn't be decoded in UTF-8, |
406 | | // fallback to local codec |
407 | 1.49M | const QString tryUtf8 = QString::fromUtf8(result); |
408 | 1.49M | if (tryUtf8.contains(QChar(0xFFFD))) { |
409 | 1.18M | QStringDecoder codec(QStringDecoder::System); |
410 | 1.18M | if (usedCS) { |
411 | 1.18M | *usedCS = updateEncodingCharset(*usedCS, cachedCharset(QByteArrayView(codec.name()))); |
412 | 1.18M | } |
413 | 1.18M | return codec.decode(result); |
414 | 1.18M | } else { |
415 | 308k | return tryUtf8; |
416 | 308k | } |
417 | 1.49M | } |
418 | | |
419 | | QByteArray KCodecs::encodeRFC2047String(QStringView src, const QByteArray &charset) |
420 | 6.54M | { |
421 | 6.54M | return KCodecs::encodeRFC2047String(src, charset, RFC2047EncodingOption::NoOption); |
422 | 6.54M | } |
423 | | |
424 | | static constexpr const char reservedCharacters[] = "\"()<>@,.;:\\[]="; |
425 | | |
426 | | QByteArray KCodecs::encodeRFC2047String(QStringView src, QByteArrayView charset, KCodecs::RFC2047EncodingOption option) |
427 | 6.54M | { |
428 | 6.54M | QByteArray result; |
429 | 6.54M | int start = 0; |
430 | 6.54M | int end = 0; |
431 | 6.54M | bool nonAscii = false; |
432 | 6.54M | bool useQEncoding = false; |
433 | | |
434 | 6.54M | QStringEncoder codec(charset.constData()); |
435 | | |
436 | 6.54M | QByteArrayView usedCS; |
437 | 6.54M | if (!codec.isValid()) { |
438 | | // no codec available => try local8Bit and hope the best ;-) |
439 | 0 | codec = QStringEncoder(QStringEncoder::System); |
440 | 0 | usedCS = codec.name(); |
441 | 6.54M | } else { |
442 | 6.54M | Q_ASSERT(codec.isValid()); |
443 | 6.54M | if (charset.isEmpty()) { |
444 | 0 | usedCS = codec.name(); |
445 | 6.54M | } else { |
446 | 6.54M | usedCS = charset; |
447 | 6.54M | } |
448 | 6.54M | } |
449 | | |
450 | 6.54M | const QByteArray encoded8Bit = [&] { // encoded8Bit must be const to not detach below! |
451 | 6.54M | QByteArray encoded8Bit = codec.encode(src); |
452 | 6.54M | if (codec.hasError()) { |
453 | 379 | usedCS = CodecNames::utf8(); |
454 | 379 | codec = QStringEncoder(QStringEncoder::Utf8); |
455 | 379 | encoded8Bit = codec.encode(src); |
456 | 379 | } |
457 | 6.54M | return encoded8Bit; |
458 | 6.54M | }(); |
459 | | |
460 | 6.54M | if (usedCS.contains("8859-")) { // use "B"-Encoding for non iso-8859-x charsets |
461 | 2.80k | useQEncoding = true; |
462 | 2.80k | } |
463 | | |
464 | 6.54M | uint encoded8BitLength = encoded8Bit.length(); |
465 | 32.8M | for (unsigned int i = 0; i < encoded8BitLength; i++) { |
466 | 32.5M | if (encoded8Bit[i] == ' ') { // encoding starts at word boundaries |
467 | 216k | start = i + 1; |
468 | 216k | } |
469 | | |
470 | | // encode escape character, for japanese encodings... |
471 | 32.5M | if (((signed char)encoded8Bit[i] < 0) || (encoded8Bit[i] == '\033') |
472 | 26.2M | || (option == RFC2047EncodingOption::EncodeReservedCharcters && (std::strchr(reservedCharacters, encoded8Bit[i]) != nullptr))) { |
473 | 6.29M | end = start; // non us-ascii char found, now we determine where to stop encoding |
474 | 6.29M | nonAscii = true; |
475 | 6.29M | break; |
476 | 6.29M | } |
477 | 32.5M | } |
478 | | |
479 | 6.54M | if (nonAscii) { |
480 | 62.9M | while ((end < encoded8Bit.length()) && (encoded8Bit[end] != ' ')) { |
481 | | // we encode complete words |
482 | 56.6M | end++; |
483 | 56.6M | } |
484 | | |
485 | 18.9M | for (int x = end; x < encoded8Bit.length(); x++) { |
486 | 12.6M | if (((signed char)encoded8Bit[x] < 0) || (encoded8Bit[x] == '\033') |
487 | 12.3M | || (option == RFC2047EncodingOption::EncodeReservedCharcters && (std::strchr(reservedCharacters, encoded8Bit[x]) != nullptr))) { |
488 | 324k | end = x; // we found another non-ascii word |
489 | | |
490 | 131M | while ((end < encoded8Bit.length()) && (encoded8Bit[end] != ' ')) { |
491 | | // we encode complete words |
492 | 131M | end++; |
493 | 131M | } |
494 | 324k | x = end; |
495 | 324k | } |
496 | 12.6M | } |
497 | | |
498 | 6.29M | result = QByteArrayView(encoded8Bit).left(start) + "=?" + usedCS; |
499 | | |
500 | 6.29M | if (useQEncoding) { |
501 | 1.29k | result += "?Q?"; |
502 | | |
503 | 1.29k | char hexcode; // "Q"-encoding implementation described in RFC 2047 |
504 | 2.05M | for (int i = start; i < end; i++) { |
505 | 2.05M | const char c = encoded8Bit[i]; |
506 | 2.05M | if (c == ' ') { // make the result readable with not MIME-capable readers |
507 | 9.70k | result += '_'; |
508 | 2.04M | } else { |
509 | 2.04M | if (((c >= 'a') && (c <= 'z')) || // paranoid mode, encode *all* special chars to avoid problems |
510 | 1.19M | ((c >= 'A') && (c <= 'Z')) || // with "From" & "To" headers |
511 | 1.41M | ((c >= '0') && (c <= '9'))) { |
512 | 1.41M | result += c; |
513 | 1.41M | } else { |
514 | 631k | result += '='; // "stolen" from KMail ;-) |
515 | 631k | hexcode = ((c & 0xF0) >> 4) + 48; |
516 | 631k | if (hexcode >= 58) { |
517 | 498k | hexcode += 7; |
518 | 498k | } |
519 | 631k | result += hexcode; |
520 | 631k | hexcode = (c & 0x0F) + 48; |
521 | 631k | if (hexcode >= 58) { |
522 | 252k | hexcode += 7; |
523 | 252k | } |
524 | 631k | result += hexcode; |
525 | 631k | } |
526 | 2.04M | } |
527 | 2.05M | } |
528 | 6.28M | } else { |
529 | 6.28M | result += "?B?" + encoded8Bit.mid(start, end - start).toBase64(); |
530 | 6.28M | } |
531 | | |
532 | 6.29M | result += "?="; |
533 | 6.29M | result += QByteArrayView(encoded8Bit).right(encoded8Bit.length() - end); |
534 | 6.29M | } else { |
535 | 256k | result = encoded8Bit; |
536 | 256k | } |
537 | | |
538 | 6.54M | return result; |
539 | 6.54M | } |
540 | | |
541 | | /******************************************************************************/ |
542 | | /* KCodecs::Codec */ |
543 | | |
544 | | KCodecs::Codec *KCodecs::Codec::codecForName(QByteArrayView name) |
545 | 511k | { |
546 | 511k | struct CodecEntry { |
547 | 511k | const char *name; |
548 | 511k | std::unique_ptr<KCodecs::Codec> codec; |
549 | 511k | }; |
550 | 511k | static const std::array<CodecEntry, 6> s_codecs{{ |
551 | 511k | {"b", std::make_unique<KCodecs::Rfc2047BEncodingCodec>()}, |
552 | 511k | {"base64", std::make_unique<KCodecs::Base64Codec>()}, |
553 | 511k | {"q", std::make_unique<KCodecs::Rfc2047QEncodingCodec>()}, |
554 | 511k | {"quoted-printable", std::make_unique<KCodecs::QuotedPrintableCodec>()}, |
555 | 511k | {"x-kmime-rfc2231", std::make_unique<KCodecs::Rfc2231EncodingCodec>()}, |
556 | 511k | {"x-uuencode", std::make_unique<KCodecs::UUCodec>()}, |
557 | 511k | }}; |
558 | | |
559 | 1.75M | for (auto &entry : s_codecs) { |
560 | 1.75M | if (name.compare(entry.name, Qt::CaseInsensitive) == 0) { |
561 | 480k | return entry.codec.get(); |
562 | 480k | } |
563 | 1.75M | } |
564 | 511k | qWarning() << "Unknown codec" << name << "requested!"; |
565 | 30.8k | return nullptr; |
566 | 511k | } |
567 | | |
568 | | bool KCodecs::Codec::encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend, NewlineType newline) const |
569 | 125k | { |
570 | | // get an encoder: |
571 | 125k | std::unique_ptr<Encoder> enc(makeEncoder(newline)); |
572 | 125k | if (!enc) { |
573 | 0 | qWarning() << "makeEncoder failed for" << name(); |
574 | 0 | return false; |
575 | 0 | } |
576 | | |
577 | | // encode and check for output buffer overflow: |
578 | 125k | while (!enc->encode(scursor, send, dcursor, dend)) { |
579 | 0 | if (dcursor == dend) { |
580 | 0 | return false; // not enough space in output buffer |
581 | 0 | } |
582 | 0 | } |
583 | | |
584 | | // finish and check for output buffer overflow: |
585 | 125k | while (!enc->finish(dcursor, dend)) { |
586 | 0 | if (dcursor == dend) { |
587 | 0 | return false; // not enough space in output buffer |
588 | 0 | } |
589 | 0 | } |
590 | | |
591 | 125k | return true; // successfully encoded. |
592 | 125k | } |
593 | | |
594 | | QByteArray KCodecs::Codec::encode(QByteArrayView src, NewlineType newline) const |
595 | 125k | { |
596 | | // allocate buffer for the worst case: |
597 | 125k | QByteArray result; |
598 | 125k | result.resize(maxEncodedSizeFor(src.size(), newline)); |
599 | | |
600 | | // set up iterators: |
601 | 125k | QByteArray::ConstIterator iit = src.begin(); |
602 | 125k | QByteArray::ConstIterator iend = src.end(); |
603 | 125k | QByteArray::Iterator oit = result.begin(); |
604 | 125k | QByteArray::ConstIterator oend = result.end(); |
605 | | |
606 | | // encode |
607 | 125k | if (!encode(iit, iend, oit, oend, newline)) { |
608 | 0 | qCritical() << name() << "codec lies about it's mEncodedSizeFor()"; |
609 | 0 | } |
610 | | |
611 | | // shrink result to actual size: |
612 | 125k | result.truncate(oit - result.begin()); |
613 | | |
614 | 125k | return result; |
615 | 125k | } |
616 | | |
617 | | QByteArray KCodecs::Codec::decode(QByteArrayView src, NewlineType newline) const |
618 | 138k | { |
619 | | // allocate buffer for the worst case: |
620 | 138k | QByteArray result; |
621 | 138k | result.resize(maxDecodedSizeFor(src.size(), newline)); |
622 | | |
623 | | // set up iterators: |
624 | 138k | QByteArray::ConstIterator iit = src.begin(); |
625 | 138k | QByteArray::ConstIterator iend = src.end(); |
626 | 138k | QByteArray::Iterator oit = result.begin(); |
627 | 138k | QByteArray::ConstIterator oend = result.end(); |
628 | | |
629 | | // decode |
630 | 138k | if (!decode(iit, iend, oit, oend, newline)) { |
631 | 0 | qCritical() << name() << "codec lies about it's maxDecodedSizeFor()"; |
632 | 0 | } |
633 | | |
634 | | // shrink result to actual size: |
635 | 138k | result.truncate(oit - result.begin()); |
636 | | |
637 | 138k | return result; |
638 | 138k | } |
639 | | |
640 | | bool KCodecs::Codec::decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend, NewlineType newline) const |
641 | 138k | { |
642 | | // get a decoder: |
643 | 138k | std::unique_ptr<Decoder> dec(makeDecoder(newline)); |
644 | 138k | assert(dec); |
645 | | |
646 | | // decode and check for output buffer overflow: |
647 | 141k | while (!dec->decode(scursor, send, dcursor, dend)) { |
648 | 2.48k | if (dcursor == dend) { |
649 | 0 | return false; // not enough space in output buffer |
650 | 0 | } |
651 | 2.48k | } |
652 | | |
653 | | // finish and check for output buffer overflow: |
654 | 138k | while (!dec->finish(dcursor, dend)) { |
655 | 0 | if (dcursor == dend) { |
656 | 0 | return false; // not enough space in output buffer |
657 | 0 | } |
658 | 0 | } |
659 | | |
660 | 138k | return true; // successfully encoded. |
661 | 138k | } |
662 | | |
663 | | /******************************************************************************/ |
664 | | /* KCodecs::Encoder */ |
665 | | |
666 | | KCodecs::EncoderPrivate::EncoderPrivate(Codec::NewlineType newline) |
667 | 125k | : outputBufferCursor(0) |
668 | 125k | , newline(newline) |
669 | 125k | { |
670 | 125k | } |
671 | | |
672 | | KCodecs::Encoder::Encoder(Codec::NewlineType newline) |
673 | 125k | : d(new KCodecs::EncoderPrivate(newline)) |
674 | 125k | { |
675 | 125k | } |
676 | | |
677 | 125k | KCodecs::Encoder::~Encoder() = default; |
678 | | |
679 | | bool KCodecs::Encoder::write(char ch, char *&dcursor, const char *const dend) |
680 | 716k | { |
681 | 716k | if (dcursor != dend) { |
682 | | // if there's space in the output stream, write there: |
683 | 716k | *dcursor++ = ch; |
684 | 716k | return true; |
685 | 716k | } else { |
686 | | // else buffer the output: |
687 | 0 | if (d->outputBufferCursor >= maxBufferedChars) { |
688 | 0 | qCritical() << "KCodecs::Encoder: internal buffer overflow!"; |
689 | 0 | } else { |
690 | 0 | d->outputBuffer[d->outputBufferCursor++] = ch; |
691 | 0 | } |
692 | 0 | return false; |
693 | 0 | } |
694 | 716k | } |
695 | | |
696 | | // write as much as possible off the output buffer. Return true if |
697 | | // flushing was complete, false if some chars could not be flushed. |
698 | | bool KCodecs::Encoder::flushOutputBuffer(char *&dcursor, const char *const dend) |
699 | 125k | { |
700 | 125k | int i; |
701 | | // copy output buffer to output stream: |
702 | 125k | for (i = 0; dcursor != dend && i < d->outputBufferCursor; ++i) { |
703 | 0 | *dcursor++ = d->outputBuffer[i]; |
704 | 0 | } |
705 | | |
706 | | // calculate the number of missing chars: |
707 | 125k | int numCharsLeft = d->outputBufferCursor - i; |
708 | | // push the remaining chars to the beginning of the buffer: |
709 | 125k | if (numCharsLeft) { |
710 | 0 | ::memmove(d->outputBuffer, d->outputBuffer + i, numCharsLeft); |
711 | 0 | } |
712 | | // adjust cursor: |
713 | 125k | d->outputBufferCursor = numCharsLeft; |
714 | | |
715 | 125k | return !numCharsLeft; |
716 | 125k | } |
717 | | |
718 | | bool KCodecs::Encoder::writeCRLF(char *&dcursor, const char *const dend) |
719 | 128k | { |
720 | 128k | if (d->newline == Codec::NewlineCRLF) { |
721 | 0 | write('\r', dcursor, dend); |
722 | 0 | } |
723 | 128k | return write('\n', dcursor, dend); |
724 | 128k | } |
725 | | |
726 | | /******************************************************************************/ |
727 | | /* KCodecs::Decoder */ |
728 | | |
729 | | KCodecs::DecoderPrivate::DecoderPrivate(Codec::NewlineType newline) |
730 | 360k | : newline(newline) |
731 | 360k | { |
732 | 360k | } |
733 | | |
734 | | KCodecs::Decoder::Decoder(Codec::NewlineType newline) |
735 | 360k | : d(new KCodecs::DecoderPrivate(newline)) |
736 | 360k | { |
737 | 360k | } |
738 | | |
739 | 360k | KCodecs::Decoder::~Decoder() = default; |