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

source code of kcodecs/src/kcodecs.cpp