File indexing completed on 2024-04-28 07:41:31

0001 /*
0002     SPDX-FileCopyrightText: 2000-2001 Dawit Alemayehu <adawit@kde.org>
0003     SPDX-FileCopyrightText: 2001 Rik Hemsley (rikkus) <rik@kde.org>
0004     SPDX-FileCopyrightText: 2001-2002 Marc Mutz <mutz@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 
0008     The encoding and decoding utilities in KCodecs with the exception of
0009     quoted-printable are based on the java implementation in HTTPClient
0010     package by Ronald Tschalär Copyright (C) 1996-1999.                          // krazy:exclude=copyright
0011 
0012     The quoted-printable codec as described in RFC 2045, section 6.7. is by
0013     Rik Hemsley (C) 2001.
0014 */
0015 
0016 #include "kcodecs.h"
0017 #include "kcharsets.h"
0018 #include "kcharsets_p.h"
0019 #include "kcodecs_debug.h"
0020 #include "kcodecs_p.h"
0021 #include "kcodecsbase64.h"
0022 #include "kcodecsqp.h"
0023 #include "kcodecsuuencode.h"
0024 
0025 #include <array>
0026 #include <cassert>
0027 #include <cstring>
0028 #include <stdio.h>
0029 #include <stdlib.h>
0030 #include <string.h>
0031 
0032 #include <QDebug>
0033 #include <QStringDecoder>
0034 #include <QStringEncoder>
0035 
0036 #if defined(Q_OS_WIN)
0037 #define strncasecmp _strnicmp
0038 #endif
0039 
0040 namespace KCodecs
0041 {
0042 static QList<QByteArray> charsetCache;
0043 
0044 QByteArray cachedCharset(const QByteArray &name)
0045 {
0046     auto it = std::find_if(charsetCache.cbegin(), charsetCache.cend(), [&name](const QByteArray &charset) {
0047         return qstricmp(name.data(), charset.data()) == 0;
0048     });
0049     if (it != charsetCache.cend()) {
0050         return *it;
0051     }
0052 
0053     charsetCache.append(name.toUpper());
0054     return charsetCache.last();
0055 }
0056 
0057 namespace CodecNames
0058 {
0059 QByteArray utf8()
0060 {
0061     return QByteArrayLiteral("UTF-8");
0062 }
0063 }
0064 
0065 Q_REQUIRED_RESULT
0066 QByteArray updateEncodingCharset(const QByteArray &currentCharset, const QByteArray &nextCharset)
0067 {
0068     if (!nextCharset.isEmpty()) {
0069         if (currentCharset.isEmpty()) {
0070             return nextCharset;
0071         }
0072         if (currentCharset != nextCharset) {
0073             // only one charset per string supported, so change to superset charset UTF-8,
0074             // which should  cover any possible chars
0075             return CodecNames::utf8();
0076         }
0077     }
0078     return currentCharset;
0079 }
0080 
0081 } // namespace KCodecs
0082 
0083 /******************************** KCodecs ********************************/
0084 
0085 QByteArray KCodecs::quotedPrintableEncode(QByteArrayView in, bool useCRLF)
0086 {
0087     Codec *codec = Codec::codecForName("quoted-printable");
0088     return codec->encode(in, useCRLF ? Codec::NewlineCRLF : Codec::NewlineLF);
0089 }
0090 
0091 void KCodecs::quotedPrintableEncode(QByteArrayView in, QByteArray &out, bool useCRLF)
0092 {
0093     out = quotedPrintableEncode(in, useCRLF ? Codec::NewlineCRLF : Codec::NewlineLF);
0094 }
0095 
0096 QByteArray KCodecs::quotedPrintableDecode(QByteArrayView in)
0097 {
0098     Codec *codec = Codec::codecForName("quoted-printable");
0099     return codec->decode(in);
0100 }
0101 
0102 void KCodecs::quotedPrintableDecode(QByteArrayView in, QByteArray &out)
0103 {
0104     out = quotedPrintableDecode(in);
0105 }
0106 
0107 QByteArray KCodecs::base64Encode(QByteArrayView in)
0108 {
0109     Codec *codec = Codec::codecForName("base64");
0110     return codec->encode(in);
0111 }
0112 
0113 void KCodecs::base64Encode(QByteArrayView in, QByteArray &out, bool insertLFs)
0114 {
0115     Q_UNUSED(insertLFs);
0116     out = base64Encode(in);
0117 }
0118 
0119 QByteArray KCodecs::base64Decode(QByteArrayView in)
0120 {
0121     Codec *codec = Codec::codecForName("base64");
0122     return codec->decode(in);
0123 }
0124 
0125 void KCodecs::base64Decode(const QByteArrayView in, QByteArray &out)
0126 {
0127     out = base64Decode(in);
0128 }
0129 
0130 QByteArray KCodecs::uudecode(QByteArrayView in)
0131 {
0132     Codec *codec = Codec::codecForName("x-uuencode");
0133     return codec->decode(in);
0134 }
0135 
0136 void KCodecs::uudecode(QByteArrayView in, QByteArray &out)
0137 {
0138     out = uudecode(in);
0139 }
0140 
0141 //@cond PRIVATE
0142 
0143 namespace KCodecs
0144 {
0145 // parse the encoded-word (scursor points to after the initial '=')
0146 bool parseEncodedWord(const char *&scursor,
0147                       const char *const send,
0148                       QString *result,
0149                       QByteArray *language,
0150                       QByteArray *usedCS,
0151                       const QByteArray &defaultCS,
0152                       CharsetOption charsetOption)
0153 {
0154     assert(result);
0155     assert(language);
0156 
0157     // make sure the caller already did a bit of the work.
0158     assert(*(scursor - 1) == '=');
0159 
0160     //
0161     // STEP 1:
0162     // scan for the charset/language portion of the encoded-word
0163     //
0164 
0165     char ch = *scursor++;
0166 
0167     if (ch != '?') {
0168         // qCDebug(KCODECS_LOG) << "first";
0169         // qCDebug(KCODECS_LOG) << "Premature end of encoded word";
0170         return false;
0171     }
0172 
0173     // remember start of charset (i.e. just after the initial "=?") and
0174     // language (just after the first '*') fields:
0175     const char *charsetStart = scursor;
0176     const char *languageStart = nullptr;
0177 
0178     // find delimiting '?' (and the '*' separating charset and language
0179     // tags, if any):
0180     for (; scursor != send; scursor++) {
0181         if (*scursor == '?') {
0182             break;
0183         } else if (*scursor == '*' && languageStart == nullptr) {
0184             languageStart = scursor + 1;
0185         }
0186     }
0187 
0188     // not found? can't be an encoded-word!
0189     if (scursor == send || *scursor != '?') {
0190         // qCDebug(KCODECS_LOG) << "second";
0191         // qCDebug(KCODECS_LOG) << "Premature end of encoded word";
0192         return false;
0193     }
0194 
0195     // extract the language information, if any (if languageStart is 0,
0196     // language will be null, too):
0197     QByteArray maybeLanguage(languageStart, scursor - languageStart);
0198     // extract charset information (keep in mind: the size given to the
0199     // ctor is one off due to the \0 terminator):
0200     QByteArray maybeCharset(charsetStart, (languageStart ? languageStart - 1 : scursor) - charsetStart);
0201 
0202     //
0203     // STEP 2:
0204     // scan for the encoding portion of the encoded-word
0205     //
0206 
0207     // remember start of encoding (just _after_ the second '?'):
0208     scursor++;
0209     const char *encodingStart = scursor;
0210 
0211     // find next '?' (ending the encoding tag):
0212     for (; scursor != send; scursor++) {
0213         if (*scursor == '?') {
0214             break;
0215         }
0216     }
0217 
0218     // not found? Can't be an encoded-word!
0219     if (scursor == send || *scursor != '?') {
0220         // qCDebug(KCODECS_LOG) << "third";
0221         // qCDebug(KCODECS_LOG) << "Premature end of encoded word";
0222         return false;
0223     }
0224 
0225     // extract the encoding information:
0226     QByteArray maybeEncoding(encodingStart, scursor - encodingStart);
0227 
0228     // qCDebug(KCODECS_LOG) << "parseEncodedWord: found charset == \"" << maybeCharset
0229     //         << "\"; language == \"" << maybeLanguage
0230     //         << "\"; encoding == \"" << maybeEncoding << "\"";
0231 
0232     //
0233     // STEP 3:
0234     // scan for encoded-text portion of encoded-word
0235     //
0236 
0237     // remember start of encoded-text (just after the third '?'):
0238     scursor++;
0239     const char *encodedTextStart = scursor;
0240 
0241     // find the '?=' sequence (ending the encoded-text):
0242     for (; scursor != send; scursor++) {
0243         if (*scursor == '?') {
0244             if (scursor + 1 != send) {
0245                 if (*(scursor + 1) != '=') { // We expect a '=' after the '?', but we got something else; ignore
0246                     // qCDebug(KCODECS_LOG) << "Stray '?' in q-encoded word, ignoring this.";
0247                     continue;
0248                 } else { // yep, found a '?=' sequence
0249                     scursor += 2;
0250                     break;
0251                 }
0252             } else { // The '?' is the last char, but we need a '=' after it!
0253                 // qCDebug(KCODECS_LOG) << "Premature end of encoded word";
0254                 return false;
0255             }
0256         }
0257     }
0258 
0259     if (*(scursor - 2) != '?' || *(scursor - 1) != '=' || scursor < encodedTextStart + 2) {
0260         // qCDebug(KCODECS_LOG) << "Premature end of encoded word";
0261         return false;
0262     }
0263 
0264     // set end sentinel for encoded-text:
0265     const char *const encodedTextEnd = scursor - 2;
0266 
0267     //
0268     // STEP 4:
0269     // setup decoders for the transfer encoding and the charset
0270     //
0271 
0272     // try if there's a codec for the encoding found:
0273     Codec *codec = Codec::codecForName(maybeEncoding);
0274     if (!codec) {
0275         // qCDebug(KCODECS_LOG) << "Unknown encoding" << maybeEncoding;
0276         return false;
0277     }
0278 
0279     // get an instance of a corresponding decoder:
0280     Decoder *dec = codec->makeDecoder();
0281     assert(dec);
0282 
0283     // try if there's a (text)codec for the charset found:
0284     QByteArray cs;
0285     QStringDecoder textCodec;
0286     if (charsetOption == KCodecs::ForceDefaultCharset || maybeCharset.isEmpty()) {
0287         textCodec = QStringDecoder(defaultCS.constData());
0288         cs = cachedCharset(defaultCS);
0289     } else {
0290         textCodec = QStringDecoder(maybeCharset.constData());
0291         if (!textCodec.isValid()) { // no suitable codec found => use default charset
0292             textCodec = QStringDecoder(defaultCS.constData());
0293             cs = cachedCharset(defaultCS);
0294         } else {
0295             cs = cachedCharset(maybeCharset);
0296         }
0297     }
0298     if (usedCS) {
0299         *usedCS = updateEncodingCharset(*usedCS, cs);
0300     }
0301 
0302     if (!textCodec.isValid()) {
0303         // qCDebug(KCODECS_LOG) << "Unknown charset" << maybeCharset;
0304         delete dec;
0305         return false;
0306     };
0307 
0308     // qCDebug(KCODECS_LOG) << "mimeName(): \"" << textCodec->name() << "\"";
0309 
0310     // allocate a temporary buffer to store the 8bit text:
0311     int encodedTextLength = encodedTextEnd - encodedTextStart;
0312     QByteArray buffer;
0313     buffer.resize(codec->maxDecodedSizeFor(encodedTextLength));
0314     char *bbegin = buffer.data();
0315     char *bend = bbegin + buffer.length();
0316 
0317     //
0318     // STEP 5:
0319     // do the actual decoding
0320     //
0321 
0322     if (!dec->decode(encodedTextStart, encodedTextEnd, bbegin, bend)) {
0323         qWarning() << codec->name() << "codec lies about its maxDecodedSizeFor(" << encodedTextLength << ")\nresult may be truncated";
0324     }
0325 
0326     *result = textCodec.decode(QByteArrayView(buffer.data(), bbegin - buffer.data()));
0327 
0328     // qCDebug(KCODECS_LOG) << "result now: \"" << result << "\"";
0329     // cleanup:
0330     delete dec;
0331     *language = maybeLanguage;
0332 
0333     return true;
0334 }
0335 
0336 } // namespace KCodecs
0337 
0338 //@endcond
0339 
0340 QString KCodecs::decodeRFC2047String(QStringView msg)
0341 {
0342     QByteArray usedCS;
0343     return decodeRFC2047String(msg.toUtf8(), &usedCS, CodecNames::utf8(), NoOption);
0344 }
0345 
0346 QString KCodecs::decodeRFC2047String(QByteArrayView src, QByteArray *usedCS, const QByteArray &defaultCS, CharsetOption charsetOption)
0347 {
0348     QByteArray result;
0349     QByteArray spaceBuffer;
0350     const char *scursor = src.constData();
0351     const char *send = scursor + src.length();
0352     bool onlySpacesSinceLastWord = false;
0353     if (usedCS) {
0354         usedCS->clear();
0355     }
0356 
0357     while (scursor != send) {
0358         // space
0359         if (isspace(*scursor) && onlySpacesSinceLastWord) {
0360             spaceBuffer += *scursor++;
0361             continue;
0362         }
0363 
0364         // possible start of an encoded word
0365         if (*scursor == '=') {
0366             QByteArray language;
0367             QString decoded;
0368             ++scursor;
0369             const char *start = scursor;
0370             if (parseEncodedWord(scursor, send, &decoded, &language, usedCS, defaultCS, charsetOption)) {
0371                 result += decoded.toUtf8();
0372                 onlySpacesSinceLastWord = true;
0373                 spaceBuffer.clear();
0374             } else {
0375                 if (onlySpacesSinceLastWord) {
0376                     result += spaceBuffer;
0377                     onlySpacesSinceLastWord = false;
0378                 }
0379                 result += '=';
0380                 scursor = start; // reset cursor after parsing failure
0381             }
0382             continue;
0383         } else {
0384             // unencoded data
0385             if (onlySpacesSinceLastWord) {
0386                 result += spaceBuffer;
0387                 onlySpacesSinceLastWord = false;
0388             }
0389             result += *scursor;
0390             ++scursor;
0391         }
0392     }
0393     // If there are any chars that couldn't be decoded in UTF-8,
0394     // fallback to local codec
0395     const QString tryUtf8 = QString::fromUtf8(result);
0396     if (tryUtf8.contains(QChar(0xFFFD))) {
0397         QStringDecoder codec(QStringDecoder::System);
0398         if (usedCS) {
0399             *usedCS = updateEncodingCharset(*usedCS, cachedCharset(codec.name()));
0400         }
0401         return codec.decode(result);
0402     } else {
0403         return tryUtf8;
0404     }
0405 }
0406 
0407 QByteArray KCodecs::encodeRFC2047String(QStringView src, const QByteArray &charset)
0408 {
0409     QByteArray result;
0410     int start = 0;
0411     int end = 0;
0412     bool nonAscii = false;
0413     bool useQEncoding = false;
0414 
0415     QStringEncoder codec(charset.constData());
0416 
0417     QByteArray usedCS;
0418     if (!codec.isValid()) {
0419         // no codec available => try local8Bit and hope the best ;-)
0420         codec = QStringEncoder(QStringEncoder::System);
0421         usedCS = codec.name();
0422     } else {
0423         Q_ASSERT(codec.isValid());
0424         if (charset.isEmpty()) {
0425             usedCS = codec.name();
0426         } else {
0427             usedCS = charset;
0428         }
0429     }
0430 
0431     QByteArray encoded8Bit = codec.encode(src);
0432     if (codec.hasError()) {
0433         usedCS = CodecNames::utf8();
0434         codec = QStringEncoder(QStringEncoder::Utf8);
0435         encoded8Bit = codec.encode(src);
0436     }
0437 
0438     if (usedCS.contains("8859-")) { // use "B"-Encoding for non iso-8859-x charsets
0439         useQEncoding = true;
0440     }
0441 
0442     uint encoded8BitLength = encoded8Bit.length();
0443     for (unsigned int i = 0; i < encoded8BitLength; i++) {
0444         if (encoded8Bit[i] == ' ') { // encoding starts at word boundaries
0445             start = i + 1;
0446         }
0447 
0448         // encode escape character, for japanese encodings...
0449         if (((signed char)encoded8Bit[i] < 0) || (encoded8Bit[i] == '\033')) {
0450             end = start; // non us-ascii char found, now we determine where to stop encoding
0451             nonAscii = true;
0452             break;
0453         }
0454     }
0455 
0456     if (nonAscii) {
0457         while ((end < encoded8Bit.length()) && (encoded8Bit[end] != ' ')) {
0458             // we encode complete words
0459             end++;
0460         }
0461 
0462         for (int x = end; x < encoded8Bit.length(); x++) {
0463             if (((signed char)encoded8Bit[x] < 0) || (encoded8Bit[x] == '\033')) {
0464                 end = x; // we found another non-ascii word
0465 
0466                 while ((end < encoded8Bit.length()) && (encoded8Bit[end] != ' ')) {
0467                     // we encode complete words
0468                     end++;
0469                 }
0470             }
0471         }
0472 
0473         result = encoded8Bit.left(start) + "=?" + usedCS;
0474 
0475         if (useQEncoding) {
0476             result += "?Q?";
0477 
0478             char hexcode; // "Q"-encoding implementation described in RFC 2047
0479             for (int i = start; i < end; i++) {
0480                 const char c = encoded8Bit[i];
0481                 if (c == ' ') { // make the result readable with not MIME-capable readers
0482                     result += '_';
0483                 } else {
0484                     if (((c >= 'a') && (c <= 'z')) || // paranoid mode, encode *all* special chars to avoid problems
0485                         ((c >= 'A') && (c <= 'Z')) || // with "From" & "To" headers
0486                         ((c >= '0') && (c <= '9'))) {
0487                         result += c;
0488                     } else {
0489                         result += '='; // "stolen" from KMail ;-)
0490                         hexcode = ((c & 0xF0) >> 4) + 48;
0491                         if (hexcode >= 58) {
0492                             hexcode += 7;
0493                         }
0494                         result += hexcode;
0495                         hexcode = (c & 0x0F) + 48;
0496                         if (hexcode >= 58) {
0497                             hexcode += 7;
0498                         }
0499                         result += hexcode;
0500                     }
0501                 }
0502             }
0503         } else {
0504             result += "?B?" + encoded8Bit.mid(start, end - start).toBase64();
0505         }
0506 
0507         result += "?=";
0508         result += encoded8Bit.right(encoded8Bit.length() - end);
0509     } else {
0510         result = encoded8Bit;
0511     }
0512 
0513     return result;
0514 }
0515 
0516 /******************************************************************************/
0517 /*                           KCodecs::Codec                                   */
0518 
0519 KCodecs::Codec *KCodecs::Codec::codecForName(QByteArrayView name)
0520 {
0521     struct CodecEntry {
0522         const char *name;
0523         std::unique_ptr<KCodecs::Codec> codec;
0524     };
0525     // ### has to be sorted by name!
0526     static const std::array<CodecEntry, 6> s_codecs{{
0527         {"b", std::make_unique<KCodecs::Rfc2047BEncodingCodec>()},
0528         {"base64", std::make_unique<KCodecs::Base64Codec>()},
0529         {"q", std::make_unique<KCodecs::Rfc2047QEncodingCodec>()},
0530         {"quoted-printable", std::make_unique<KCodecs::QuotedPrintableCodec>()},
0531         {"x-kmime-rfc2231", std::make_unique<KCodecs::Rfc2231EncodingCodec>()},
0532         {"x-uuencode", std::make_unique<KCodecs::UUCodec>()},
0533     }};
0534 
0535     const auto it = std::lower_bound(s_codecs.begin(), s_codecs.end(), name, [](const auto &lhs, auto rhs) {
0536         return rhs.compare(lhs.name, Qt::CaseInsensitive) > 0;
0537     });
0538     if (it == s_codecs.end() || name.compare((*it).name, Qt::CaseInsensitive) != 0) {
0539         qWarning() << "Unknown codec \"" << name << "\" requested!";
0540     }
0541     return (*it).codec.get();
0542 }
0543 
0544 bool KCodecs::Codec::encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend, NewlineType newline) const
0545 {
0546     // get an encoder:
0547     std::unique_ptr<Encoder> enc(makeEncoder(newline));
0548     if (!enc) {
0549         qWarning() << "makeEncoder failed for" << name();
0550         return false;
0551     }
0552 
0553     // encode and check for output buffer overflow:
0554     while (!enc->encode(scursor, send, dcursor, dend)) {
0555         if (dcursor == dend) {
0556             return false; // not enough space in output buffer
0557         }
0558     }
0559 
0560     // finish and check for output buffer overflow:
0561     while (!enc->finish(dcursor, dend)) {
0562         if (dcursor == dend) {
0563             return false; // not enough space in output buffer
0564         }
0565     }
0566 
0567     return true; // successfully encoded.
0568 }
0569 
0570 QByteArray KCodecs::Codec::encode(QByteArrayView src, NewlineType newline) const
0571 {
0572     // allocate buffer for the worst case:
0573     QByteArray result;
0574     result.resize(maxEncodedSizeFor(src.size(), newline));
0575 
0576     // set up iterators:
0577     QByteArray::ConstIterator iit = src.begin();
0578     QByteArray::ConstIterator iend = src.end();
0579     QByteArray::Iterator oit = result.begin();
0580     QByteArray::ConstIterator oend = result.end();
0581 
0582     // encode
0583     if (!encode(iit, iend, oit, oend, newline)) {
0584         qCritical() << name() << "codec lies about it's mEncodedSizeFor()";
0585     }
0586 
0587     // shrink result to actual size:
0588     result.truncate(oit - result.begin());
0589 
0590     return result;
0591 }
0592 
0593 QByteArray KCodecs::Codec::decode(QByteArrayView src, NewlineType newline) const
0594 {
0595     // allocate buffer for the worst case:
0596     QByteArray result;
0597     result.resize(maxDecodedSizeFor(src.size(), newline));
0598 
0599     // set up iterators:
0600     QByteArray::ConstIterator iit = src.begin();
0601     QByteArray::ConstIterator iend = src.end();
0602     QByteArray::Iterator oit = result.begin();
0603     QByteArray::ConstIterator oend = result.end();
0604 
0605     // decode
0606     if (!decode(iit, iend, oit, oend, newline)) {
0607         qCritical() << name() << "codec lies about it's maxDecodedSizeFor()";
0608     }
0609 
0610     // shrink result to actual size:
0611     result.truncate(oit - result.begin());
0612 
0613     return result;
0614 }
0615 
0616 bool KCodecs::Codec::decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend, NewlineType newline) const
0617 {
0618     // get a decoder:
0619     std::unique_ptr<Decoder> dec(makeDecoder(newline));
0620     assert(dec);
0621 
0622     // decode and check for output buffer overflow:
0623     while (!dec->decode(scursor, send, dcursor, dend)) {
0624         if (dcursor == dend) {
0625             return false; // not enough space in output buffer
0626         }
0627     }
0628 
0629     // finish and check for output buffer overflow:
0630     while (!dec->finish(dcursor, dend)) {
0631         if (dcursor == dend) {
0632             return false; // not enough space in output buffer
0633         }
0634     }
0635 
0636     return true; // successfully encoded.
0637 }
0638 
0639 /******************************************************************************/
0640 /*                          KCodecs::Encoder                                  */
0641 
0642 KCodecs::EncoderPrivate::EncoderPrivate(Codec::NewlineType newline)
0643     : outputBufferCursor(0)
0644     , newline(newline)
0645 {
0646 }
0647 
0648 KCodecs::Encoder::Encoder(Codec::NewlineType newline)
0649     : d(new KCodecs::EncoderPrivate(newline))
0650 {
0651 }
0652 
0653 KCodecs::Encoder::~Encoder() = default;
0654 
0655 bool KCodecs::Encoder::write(char ch, char *&dcursor, const char *const dend)
0656 {
0657     if (dcursor != dend) {
0658         // if there's space in the output stream, write there:
0659         *dcursor++ = ch;
0660         return true;
0661     } else {
0662         // else buffer the output:
0663         if (d->outputBufferCursor >= maxBufferedChars) {
0664             qCritical() << "KCodecs::Encoder: internal buffer overflow!";
0665         } else {
0666             d->outputBuffer[d->outputBufferCursor++] = ch;
0667         }
0668         return false;
0669     }
0670 }
0671 
0672 // write as much as possible off the output buffer. Return true if
0673 // flushing was complete, false if some chars could not be flushed.
0674 bool KCodecs::Encoder::flushOutputBuffer(char *&dcursor, const char *const dend)
0675 {
0676     int i;
0677     // copy output buffer to output stream:
0678     for (i = 0; dcursor != dend && i < d->outputBufferCursor; ++i) {
0679         *dcursor++ = d->outputBuffer[i];
0680     }
0681 
0682     // calculate the number of missing chars:
0683     int numCharsLeft = d->outputBufferCursor - i;
0684     // push the remaining chars to the beginning of the buffer:
0685     if (numCharsLeft) {
0686         ::memmove(d->outputBuffer, d->outputBuffer + i, numCharsLeft);
0687     }
0688     // adjust cursor:
0689     d->outputBufferCursor = numCharsLeft;
0690 
0691     return !numCharsLeft;
0692 }
0693 
0694 bool KCodecs::Encoder::writeCRLF(char *&dcursor, const char *const dend)
0695 {
0696     if (d->newline == Codec::NewlineCRLF) {
0697         write('\r', dcursor, dend);
0698     }
0699     return write('\n', dcursor, dend);
0700 }
0701 
0702 /******************************************************************************/
0703 /*                           KCodecs::Decoder                                 */
0704 
0705 KCodecs::DecoderPrivate::DecoderPrivate(Codec::NewlineType newline)
0706     : newline(newline)
0707 {
0708 }
0709 
0710 KCodecs::Decoder::Decoder(Codec::NewlineType newline)
0711     : d(new KCodecs::DecoderPrivate(newline))
0712 {
0713 }
0714 
0715 KCodecs::Decoder::~Decoder() = default;