File indexing completed on 2024-03-24 15:25:34

0001 /*  -*- c++ -*-
0002     SPDX-FileCopyrightText: 2002 Marc Mutz <mutz@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 /**
0007   @file
0008   This file is part of the API for handling @ref MIME data and
0009   defines the @ref QuotedPrintable, @ref  RFC2047Q, and
0010   @ref RFC2231 @ref Codec classes.
0011 
0012   @brief
0013   Defines the classes QuotedPrintableCodec, Rfc2047QEncodingCodec, and
0014   Rfc2231EncodingCodec.
0015 
0016   @authors Marc Mutz \<mutz@kde.org\>
0017 */
0018 
0019 #include "kcodecsqp.h"
0020 #include "kcodecs_p.h"
0021 
0022 #include <QDebug>
0023 
0024 #include <cassert>
0025 
0026 using namespace KCodecs;
0027 
0028 namespace KCodecs
0029 {
0030 // none except a-zA-Z0-9!*+-/
0031 const uchar eTextMap[16] = {0x00, 0x00, 0x00, 0x00, 0x40, 0x35, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xE0};
0032 
0033 // some helpful functions:
0034 
0035 /**
0036   Converts a 4-bit @p value into its hexadecimal characater representation.
0037   So input of value [0,15] returns ['0','1',... 'F'].  Input values
0038   greater than 15 will produce undesired results.
0039   @param value is an unsigned character containing the 4-bit input value.
0040 */
0041 static inline char binToHex(uchar value)
0042 {
0043     if (value > 9) {
0044         return value + 'A' - 10;
0045     } else {
0046         return value + '0';
0047     }
0048 }
0049 
0050 /**
0051   Returns the high-order 4 bits of an 8-bit value in another 8-bit value.
0052   @param ch is an unsigned character containing the 8-bit input value.
0053 */
0054 static inline uchar highNibble(uchar ch)
0055 {
0056     return ch >> 4;
0057 }
0058 
0059 /**
0060   Returns the low-order 4 bits of an 8-bit value in another 8-bit value.
0061   @param ch is an unsigned character containing the 8-bit input value.
0062 */
0063 static inline uchar lowNibble(uchar ch)
0064 {
0065     return ch & 0xF;
0066 }
0067 
0068 /**
0069   Returns true if the specified value is a not Control character or
0070   question mark; else true.
0071   @param ch is an unsigned character containing the 8-bit input value.
0072 */
0073 static inline bool keep(uchar ch)
0074 {
0075     // no CTLs, except HT and not '?'
0076     return !((ch < ' ' && ch != '\t') || ch == '?');
0077 }
0078 
0079 //
0080 // QuotedPrintableCodec
0081 //
0082 
0083 class QuotedPrintableEncoder : public Encoder
0084 {
0085     char mInputBuffer[16];
0086     uchar mCurrentLineLength; // 0..76
0087     uchar mAccu;
0088     uint mInputBufferReadCursor : 4; // 0..15
0089     uint mInputBufferWriteCursor : 4; // 0..15
0090     enum {
0091         Never,
0092         AtBOL,
0093         Definitely,
0094     } mAccuNeedsEncoding : 2;
0095     bool mSawLineEnd : 1;
0096     bool mSawCR : 1;
0097     bool mFinishing : 1;
0098     bool mFinished : 1;
0099 
0100 protected:
0101     friend class QuotedPrintableCodec;
0102     QuotedPrintableEncoder(Codec::NewlineType newline = Codec::NewlineLF)
0103         : Encoder(newline)
0104         , mCurrentLineLength(0)
0105         , mAccu(0)
0106         , mInputBufferReadCursor(0)
0107         , mInputBufferWriteCursor(0)
0108         , mAccuNeedsEncoding(Never)
0109         , mSawLineEnd(false)
0110         , mSawCR(false)
0111         , mFinishing(false)
0112         , mFinished(false)
0113     {
0114     }
0115 
0116     bool needsEncoding(uchar ch)
0117     {
0118         return ch > '~' || (ch < ' ' && ch != '\t') || ch == '=';
0119     }
0120     bool needsEncodingAtEOL(uchar ch)
0121     {
0122         return ch == ' ' || ch == '\t';
0123     }
0124     bool needsEncodingAtBOL(uchar ch)
0125     {
0126         return ch == 'F' || ch == '.' || ch == '-';
0127     }
0128     bool fillInputBuffer(const char *&scursor, const char *const send);
0129     bool processNextChar();
0130     void createOutputBuffer(char *&dcursor, const char *const dend);
0131 
0132 public:
0133     ~QuotedPrintableEncoder() override
0134     {
0135     }
0136 
0137     bool encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) override;
0138 
0139     bool finish(char *&dcursor, const char *const dend) override;
0140 };
0141 
0142 class QuotedPrintableDecoder : public Decoder
0143 {
0144     const char mEscapeChar;
0145     char mBadChar;
0146     /** @p accu holds the msb nibble of the hexchar or zero. */
0147     uchar mAccu;
0148     /** @p insideHexChar is true iff we're inside an hexchar (=XY).
0149         Together with @ref mAccu, we can build this states:
0150         @li @p insideHexChar == @p false:
0151         normal text
0152         @li @p insideHexChar == @p true, @p mAccu == 0:
0153         saw the leading '='
0154         @li @p insideHexChar == @p true, @p mAccu != 0:
0155         saw the first nibble '=X'
0156     */
0157     const bool mQEncoding;
0158     bool mInsideHexChar;
0159     bool mFlushing;
0160     bool mExpectLF;
0161     bool mHaveAccu;
0162     /** @p mLastChar holds the first char of an encoded char, so that
0163         we are able to keep the first char if the second char is invalid. */
0164     char mLastChar;
0165 
0166 protected:
0167     friend class QuotedPrintableCodec;
0168     friend class Rfc2047QEncodingCodec;
0169     friend class Rfc2231EncodingCodec;
0170     QuotedPrintableDecoder(Codec::NewlineType newline = Codec::NewlineLF, bool aQEncoding = false, char aEscapeChar = '=')
0171         : Decoder(newline)
0172         , mEscapeChar(aEscapeChar)
0173         , mBadChar(0)
0174         , mAccu(0)
0175         , mQEncoding(aQEncoding)
0176         , mInsideHexChar(false)
0177         , mFlushing(false)
0178         , mExpectLF(false)
0179         , mHaveAccu(false)
0180         , mLastChar(0)
0181     {
0182     }
0183 
0184 public:
0185     ~QuotedPrintableDecoder() override
0186     {
0187     }
0188 
0189     bool decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) override;
0190     bool finish(char *&dcursor, const char *const dend) override;
0191 };
0192 
0193 class Rfc2047QEncodingEncoder : public Encoder
0194 {
0195     uchar mAccu;
0196     uchar mStepNo;
0197     const char mEscapeChar;
0198     bool mInsideFinishing : 1;
0199 
0200 protected:
0201     friend class Rfc2047QEncodingCodec;
0202     friend class Rfc2231EncodingCodec;
0203     Rfc2047QEncodingEncoder(Codec::NewlineType newline = Codec::NewlineLF, char aEscapeChar = '=')
0204         : Encoder(newline)
0205         , mAccu(0)
0206         , mStepNo(0)
0207         , mEscapeChar(aEscapeChar)
0208         , mInsideFinishing(false)
0209     {
0210         // else an optimization in ::encode might break.
0211         assert(aEscapeChar == '=' || aEscapeChar == '%');
0212     }
0213 
0214     bool isEText(uchar ch)
0215     {
0216         return (ch < 128) && (eTextMap[ch / 8] & 0x80 >> ch % 8);
0217     }
0218 
0219     // this code assumes that isEText( mEscapeChar ) == false!
0220     bool needsEncoding(uchar ch)
0221     {
0222         if (ch > 'z') {
0223             return true; // {|}~ DEL and 8bit chars need
0224         }
0225         if (!isEText(ch)) {
0226             return true; // all but a-zA-Z0-9!/*+- need, too
0227         }
0228         if (mEscapeChar == '%' && (ch == '*' || ch == '/')) {
0229             return true; // not allowed in rfc2231 encoding
0230         }
0231         return false;
0232     }
0233 
0234 public:
0235     ~Rfc2047QEncodingEncoder() override
0236     {
0237     }
0238 
0239     bool encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) override;
0240     bool finish(char *&dcursor, const char *const dend) override;
0241 };
0242 
0243 // this doesn't access any member variables, so it can be defined static
0244 // but then we can't call it from virtual functions
0245 static int QuotedPrintableDecoder_maxDecodedSizeFor(int insize, Codec::NewlineType newline)
0246 {
0247     // all chars unencoded:
0248     int result = insize;
0249     // but maybe all of them are \n and we need to make them \r\n :-o
0250     if (newline == Codec::NewlineCRLF) {
0251         result += insize;
0252     }
0253 
0254     // there might be an accu plus escape
0255     result += 2;
0256 
0257     return result;
0258 }
0259 
0260 Encoder *QuotedPrintableCodec::makeEncoder(Codec::NewlineType newline) const
0261 {
0262     return new QuotedPrintableEncoder(newline);
0263 }
0264 
0265 Decoder *QuotedPrintableCodec::makeDecoder(Codec::NewlineType newline) const
0266 {
0267     return new QuotedPrintableDecoder(newline);
0268 }
0269 
0270 int QuotedPrintableCodec::maxDecodedSizeFor(int insize, Codec::NewlineType newline) const
0271 {
0272     return QuotedPrintableDecoder_maxDecodedSizeFor(insize, newline);
0273 }
0274 
0275 Encoder *Rfc2047QEncodingCodec::makeEncoder(Codec::NewlineType newline) const
0276 {
0277     return new Rfc2047QEncodingEncoder(newline);
0278 }
0279 
0280 Decoder *Rfc2047QEncodingCodec::makeDecoder(Codec::NewlineType newline) const
0281 {
0282     return new QuotedPrintableDecoder(newline, true);
0283 }
0284 
0285 int Rfc2047QEncodingCodec::maxDecodedSizeFor(int insize, Codec::NewlineType newline) const
0286 {
0287     return QuotedPrintableDecoder_maxDecodedSizeFor(insize, newline);
0288 }
0289 
0290 Encoder *Rfc2231EncodingCodec::makeEncoder(Codec::NewlineType newline) const
0291 {
0292     return new Rfc2047QEncodingEncoder(newline, '%');
0293 }
0294 
0295 Decoder *Rfc2231EncodingCodec::makeDecoder(Codec::NewlineType newline) const
0296 {
0297     return new QuotedPrintableDecoder(newline, true, '%');
0298 }
0299 
0300 int Rfc2231EncodingCodec::maxDecodedSizeFor(int insize, Codec::NewlineType newline) const
0301 {
0302     return QuotedPrintableDecoder_maxDecodedSizeFor(insize, newline);
0303 }
0304 
0305 /********************************************************/
0306 /********************************************************/
0307 /********************************************************/
0308 
0309 bool QuotedPrintableDecoder::decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend)
0310 {
0311     if (d->newline == Codec::NewlineCRLF) {
0312         qWarning() << "CRLF output for decoders isn't yet supported!";
0313     }
0314 
0315     while (scursor != send && dcursor != dend) {
0316         if (mFlushing) {
0317             // we have to flush chars in the aftermath of an decoding
0318             // error. The way to request a flush is to
0319             // - store the offending character in mBadChar and
0320             // - set mFlushing to true.
0321             // The supported cases are (H: hexchar, X: bad char):
0322             // =X, =HX, CR
0323             // mBadChar is only written out if it is not by itself illegal in
0324             // quoted-printable (e.g. CTLs, 8Bits).
0325             // A fast way to suppress mBadChar output is to set it to NUL.
0326             if (mInsideHexChar) {
0327                 // output '='
0328                 *dcursor++ = mEscapeChar;
0329                 mInsideHexChar = false;
0330             } else if (mHaveAccu) {
0331                 // output the high nibble of the accumulator:
0332                 *dcursor++ = mLastChar;
0333                 mHaveAccu = false;
0334                 mAccu = 0;
0335             } else {
0336                 // output mBadChar
0337                 assert(mAccu == 0);
0338                 if (mBadChar) {
0339                     if (mBadChar == '=') {
0340                         mInsideHexChar = true;
0341                     } else {
0342                         *dcursor++ = mBadChar;
0343                     }
0344                     mBadChar = 0;
0345                 }
0346                 mFlushing = false;
0347             }
0348             continue;
0349         }
0350         assert(mBadChar == 0);
0351 
0352         uchar ch = *scursor++;
0353         uchar value = 255;
0354 
0355         if (mExpectLF && ch != '\n') {
0356             // qWarning() << "QuotedPrintableDecoder:"
0357             //              "illegally formed soft linebreak or lonely CR!";
0358             mInsideHexChar = false;
0359             mExpectLF = false;
0360             if (mAccu != 0) {
0361                 return false;
0362             }
0363         }
0364 
0365         if (mInsideHexChar) {
0366             // next char(s) represent nibble instead of itself:
0367             if (ch <= '9') {
0368                 if (ch >= '0') {
0369                     value = ch - '0';
0370                 } else {
0371                     switch (ch) {
0372                     case '\r':
0373                         mExpectLF = true;
0374                         break;
0375                     case '\n':
0376                         // soft line break, but only if mAccu is NUL.
0377                         if (!mHaveAccu) {
0378                             mExpectLF = false;
0379                             mInsideHexChar = false;
0380                             break;
0381                         }
0382                     // else fall through
0383                     default:
0384                         // qWarning() << "QuotedPrintableDecoder:"
0385                         //              "illegally formed hex char! Outputting verbatim.";
0386                         mBadChar = ch;
0387                         mFlushing = true;
0388                     }
0389                     continue;
0390                 }
0391             } else { // ch > '9'
0392                 if (ch <= 'F') {
0393                     if (ch >= 'A') {
0394                         value = 10 + ch - 'A';
0395                     } else { // [:-@]
0396                         mBadChar = ch;
0397                         mFlushing = true;
0398                         continue;
0399                     }
0400                 } else { // ch > 'F'
0401                     if (ch <= 'f' && ch >= 'a') {
0402                         value = 10 + ch - 'a';
0403                     } else {
0404                         mBadChar = ch;
0405                         mFlushing = true;
0406                         continue;
0407                     }
0408                 }
0409             }
0410 
0411             assert(value < 16);
0412             assert(mBadChar == 0);
0413             assert(!mExpectLF);
0414 
0415             if (mHaveAccu) {
0416                 *dcursor++ = char(mAccu | value);
0417                 mAccu = 0;
0418                 mHaveAccu = false;
0419                 mInsideHexChar = false;
0420             } else {
0421                 mHaveAccu = true;
0422                 mAccu = value << 4;
0423                 mLastChar = ch;
0424             }
0425         } else { // not mInsideHexChar
0426             if ((ch <= '~' && ch >= ' ') || ch == '\t') {
0427                 if (ch == mEscapeChar) {
0428                     mInsideHexChar = true;
0429                 } else if (mQEncoding && ch == '_') {
0430                     *dcursor++ = char(0x20);
0431                 } else {
0432                     *dcursor++ = char(ch);
0433                 }
0434             } else if (ch == '\n') {
0435                 *dcursor++ = '\n';
0436                 mExpectLF = false;
0437             } else if (ch == '\r') {
0438                 mExpectLF = true;
0439             } else {
0440                 // qWarning() << "QuotedPrintableDecoder:" << ch <<
0441                 //  "illegal character in input stream!";
0442                 *dcursor++ = char(ch);
0443             }
0444         }
0445     }
0446 
0447     return scursor == send;
0448 }
0449 
0450 bool QuotedPrintableDecoder::finish(char *&dcursor, const char *const dend)
0451 {
0452     while ((mInsideHexChar || mHaveAccu || mFlushing) && dcursor != dend) {
0453         // we have to flush chars
0454         if (mInsideHexChar) {
0455             // output '='
0456             *dcursor++ = mEscapeChar;
0457             mInsideHexChar = false;
0458         } else if (mHaveAccu) {
0459             // output the high nibble of the accumulator:
0460             *dcursor++ = mLastChar;
0461             mHaveAccu = false;
0462             mAccu = 0;
0463         } else {
0464             // output mBadChar
0465             assert(mAccu == 0);
0466             if (mBadChar) {
0467                 *dcursor++ = mBadChar;
0468                 mBadChar = 0;
0469             }
0470             mFlushing = false;
0471         }
0472     }
0473 
0474     // return false if we are not finished yet; note that mInsideHexChar is always false
0475     return !(mHaveAccu || mFlushing);
0476 }
0477 
0478 bool QuotedPrintableEncoder::fillInputBuffer(const char *&scursor, const char *const send)
0479 {
0480     // Don't read more if there's still a tail of a line in the buffer:
0481     if (mSawLineEnd) {
0482         return true;
0483     }
0484 
0485     // Read until the buffer is full or we have found CRLF or LF (which
0486     // don't end up in the input buffer):
0487     for (; (mInputBufferWriteCursor + 1) % 16 != mInputBufferReadCursor && scursor != send; mInputBufferWriteCursor++) {
0488         char ch = *scursor++;
0489         if (ch == '\r') {
0490             mSawCR = true;
0491         } else if (ch == '\n') {
0492             // remove the CR from the input buffer (if any) and return that
0493             // we found a line ending:
0494             if (mSawCR) {
0495                 mSawCR = false;
0496                 assert(mInputBufferWriteCursor != mInputBufferReadCursor);
0497                 mInputBufferWriteCursor--;
0498             }
0499             mSawLineEnd = true;
0500             return true; // saw CRLF or LF
0501         } else {
0502             mSawCR = false;
0503         }
0504         mInputBuffer[mInputBufferWriteCursor] = ch;
0505     }
0506     mSawLineEnd = false;
0507     return false; // didn't see a line ending...
0508 }
0509 
0510 bool QuotedPrintableEncoder::processNextChar()
0511 {
0512     // If we process a buffer which doesn't end in a line break, we
0513     // can't process all of it, since the next chars that will be read
0514     // could be a line break. So we empty the buffer only until a fixed
0515     // number of chars is left (except when mFinishing, which means that
0516     // the data doesn't end in newline):
0517     const int minBufferFillWithoutLineEnd = 4;
0518 
0519     assert(d->outputBufferCursor == 0);
0520 
0521     int bufferFill = int(mInputBufferWriteCursor) - int(mInputBufferReadCursor);
0522     if (bufferFill < 0) {
0523         bufferFill += 16;
0524     }
0525 
0526     assert(bufferFill >= 0 && bufferFill <= 15);
0527 
0528     if (!mFinishing //
0529         && !mSawLineEnd //
0530         && bufferFill < minBufferFillWithoutLineEnd) {
0531         return false;
0532     }
0533 
0534     // buffer is empty, return false:
0535     if (mInputBufferReadCursor == mInputBufferWriteCursor) {
0536         return false;
0537     }
0538 
0539     // Real processing goes here:
0540     mAccu = mInputBuffer[mInputBufferReadCursor++];
0541     if (needsEncoding(mAccu)) { // always needs encoding or
0542         mAccuNeedsEncoding = Definitely;
0543     } else if ((mSawLineEnd || mFinishing) // needs encoding at end of line
0544                && bufferFill == 1 // or end of buffer
0545                && needsEncodingAtEOL(mAccu)) {
0546         mAccuNeedsEncoding = Definitely;
0547     } else if (needsEncodingAtBOL(mAccu)) {
0548         mAccuNeedsEncoding = AtBOL;
0549     } else {
0550         // never needs encoding
0551         mAccuNeedsEncoding = Never;
0552     }
0553 
0554     return true;
0555 }
0556 
0557 // Outputs processed (verbatim or hex-encoded) chars and inserts soft
0558 // line breaks as necessary. Depends on processNextChar's directions
0559 // on whether or not to encode the current char, and whether or not
0560 // the current char is the last one in it's input line:
0561 void QuotedPrintableEncoder::createOutputBuffer(char *&dcursor, const char *const dend)
0562 {
0563     const int maxLineLength = 76; // rfc 2045
0564 
0565     assert(d->outputBufferCursor == 0);
0566 
0567     /* clang-format off */
0568     bool lastOneOnThisLine = mSawLineEnd
0569                              && mInputBufferReadCursor == mInputBufferWriteCursor;
0570     /* clang-format on */
0571 
0572     int neededSpace = 1;
0573     if (mAccuNeedsEncoding == Definitely) {
0574         neededSpace = 3;
0575     }
0576 
0577     // reserve space for the soft hyphen (=)
0578     if (!lastOneOnThisLine) {
0579         neededSpace++;
0580     }
0581 
0582     if (mCurrentLineLength > maxLineLength - neededSpace) {
0583         // current line too short, insert soft line break:
0584         write('=', dcursor, dend);
0585         writeCRLF(dcursor, dend);
0586         mCurrentLineLength = 0;
0587     }
0588 
0589     if (Never == mAccuNeedsEncoding //
0590         || (AtBOL == mAccuNeedsEncoding && mCurrentLineLength != 0)) {
0591         write(mAccu, dcursor, dend);
0592         mCurrentLineLength++;
0593     } else {
0594         write('=', dcursor, dend);
0595         write(binToHex(highNibble(mAccu)), dcursor, dend);
0596         write(binToHex(lowNibble(mAccu)), dcursor, dend);
0597         mCurrentLineLength += 3;
0598     }
0599 }
0600 
0601 bool QuotedPrintableEncoder::encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend)
0602 {
0603     // support probing by the caller:
0604     if (mFinishing) {
0605         return true;
0606     }
0607 
0608     while (scursor != send && dcursor != dend) {
0609         if (d->outputBufferCursor && !flushOutputBuffer(dcursor, dend)) {
0610             return scursor == send;
0611         }
0612 
0613         assert(d->outputBufferCursor == 0);
0614 
0615         // fill input buffer until eol has been reached or until the
0616         // buffer is full, whatever comes first:
0617         fillInputBuffer(scursor, send);
0618 
0619         if (processNextChar()) {
0620             // there was one...
0621             createOutputBuffer(dcursor, dend);
0622         } else if (mSawLineEnd && mInputBufferWriteCursor == mInputBufferReadCursor) {
0623             // load a hard line break into output buffer:
0624             writeCRLF(dcursor, dend);
0625             // signal fillInputBuffer() we are ready for the next line:
0626             mSawLineEnd = false;
0627             mCurrentLineLength = 0;
0628         } else {
0629             // we are supposedly finished with this input block:
0630             break;
0631         }
0632     }
0633 
0634     // make sure we write as much as possible and don't stop _writing_
0635     // just because we have no more _input_:
0636     if (d->outputBufferCursor) {
0637         flushOutputBuffer(dcursor, dend);
0638     }
0639 
0640     return scursor == send;
0641 
0642 } // encode
0643 
0644 bool QuotedPrintableEncoder::finish(char *&dcursor, const char *const dend)
0645 {
0646     mFinishing = true;
0647 
0648     if (mFinished) {
0649         return flushOutputBuffer(dcursor, dend);
0650     }
0651 
0652     while (dcursor != dend) {
0653         if (d->outputBufferCursor && !flushOutputBuffer(dcursor, dend)) {
0654             return false;
0655         }
0656 
0657         assert(d->outputBufferCursor == 0);
0658 
0659         if (processNextChar()) {
0660             // there was one...
0661             createOutputBuffer(dcursor, dend);
0662         } else if (mSawLineEnd && mInputBufferWriteCursor == mInputBufferReadCursor) {
0663             // load a hard line break into output buffer:
0664             writeCRLF(dcursor, dend);
0665             mSawLineEnd = false;
0666             mCurrentLineLength = 0;
0667         } else {
0668             mFinished = true;
0669             return flushOutputBuffer(dcursor, dend);
0670         }
0671     }
0672 
0673     return mFinished && !d->outputBufferCursor;
0674 
0675 } // finish
0676 
0677 bool Rfc2047QEncodingEncoder::encode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend)
0678 {
0679     if (mInsideFinishing) {
0680         return true;
0681     }
0682 
0683     while (scursor != send && dcursor != dend) {
0684         uchar value = 0;
0685         switch (mStepNo) {
0686         case 0:
0687             // read the next char and decide if and how do encode:
0688             mAccu = *scursor++;
0689             if (!needsEncoding(mAccu)) {
0690                 *dcursor++ = char(mAccu);
0691             } else if (mEscapeChar == '=' && mAccu == 0x20) {
0692                 // shortcut encoding for 0x20 (latin-1/us-ascii SPACE)
0693                 // (not for rfc2231 encoding)
0694                 *dcursor++ = '_';
0695             } else {
0696                 // needs =XY encoding - write escape char:
0697                 *dcursor++ = mEscapeChar;
0698                 mStepNo = 1;
0699             }
0700             continue;
0701         case 1:
0702             // extract hi-nibble:
0703             value = highNibble(mAccu);
0704             mStepNo = 2;
0705             break;
0706         case 2:
0707             // extract lo-nibble:
0708             value = lowNibble(mAccu);
0709             mStepNo = 0;
0710             break;
0711         default:
0712             assert(0);
0713         }
0714 
0715         // and write:
0716         *dcursor++ = binToHex(value);
0717     }
0718 
0719     return scursor == send;
0720 } // encode
0721 
0722 bool Rfc2047QEncodingEncoder::finish(char *&dcursor, const char *const dend)
0723 {
0724     mInsideFinishing = true;
0725 
0726     // write the last bits of mAccu, if any:
0727     while (mStepNo != 0 && dcursor != dend) {
0728         uchar value = 0;
0729         switch (mStepNo) {
0730         case 1:
0731             // extract hi-nibble:
0732             value = highNibble(mAccu);
0733             mStepNo = 2;
0734             break;
0735         case 2:
0736             // extract lo-nibble:
0737             value = lowNibble(mAccu);
0738             mStepNo = 0;
0739             break;
0740         default:
0741             assert(0);
0742         }
0743 
0744         // and write:
0745         *dcursor++ = binToHex(value);
0746     }
0747 
0748     return mStepNo == 0;
0749 }
0750 
0751 } // namespace KCodecs