Coverage Report

Created: 2026-11-12 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 &currentCharset, 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;