/* -*- c++ -*- SPDX-FileCopyrightText: 2002 Marc Mutz SPDX-License-Identifier: LGPL-2.0-or-later */ /** @file This file is part of the API for handling @ref MIME data and defines a @ref uuencode @ref Codec class. @brief Defines the UUCodec class. @authors Marc Mutz \ */ #include "kcodecsuuencode.h" #include #include using namespace KCodecs; namespace KCodecs { class UUDecoder : public Decoder { uint mStepNo; uchar mAnnouncedOctetCount; // (on current line) uchar mCurrentOctetCount; // (on current line) uchar mOutbits; bool mLastWasCRLF : 1; bool mSawBegin : 1; // whether we already saw ^begin... uint mIntoBeginLine : 3; // count #chars we compared against "begin" 0..5 bool mSawEnd : 1; // whether we already saw ^end... uint mIntoEndLine : 2; // count #chars we compared against "end" 0..3 void searchForBegin(const char *&scursor, const char *const send); protected: friend class UUCodec; UUDecoder(Codec::NewlineType newline = Codec::NewlineLF) : Decoder(newline) , mStepNo(0) , mAnnouncedOctetCount(0) , mCurrentOctetCount(0) , mOutbits(0) , mLastWasCRLF(true) , mSawBegin(false) , mIntoBeginLine(0) , mSawEnd(false) , mIntoEndLine(0) { } public: ~UUDecoder() override { } bool decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) override; // ### really needs no finishing??? bool finish(char *&dcursor, const char *const dend) override { Q_UNUSED(dcursor); Q_UNUSED(dend); return true; } }; Encoder *UUCodec::makeEncoder(NewlineType newline) const { Q_UNUSED(newline) return nullptr; // encoding not supported } Decoder *UUCodec::makeDecoder(NewlineType newline) const { return new UUDecoder(newline); } /********************************************************/ /********************************************************/ /********************************************************/ void UUDecoder::searchForBegin(const char *&scursor, const char *const send) { static const char begin[] = "begin\n"; static const uint beginLength = 5; // sic! assert(!mSawBegin || mIntoBeginLine > 0); while (scursor != send) { uchar ch = *scursor++; if (ch == begin[mIntoBeginLine]) { if (mIntoBeginLine < beginLength) { // found another char ++mIntoBeginLine; if (mIntoBeginLine == beginLength) { mSawBegin = true; // "begin" complete, now search the next \n... } } else { // mIntoBeginLine == beginLength // found '\n': begin line complete mLastWasCRLF = true; mIntoBeginLine = 0; return; } } else if (mSawBegin) { // OK, skip stuff until the next \n } else { // qWarning() << "UUDecoder: garbage before \"begin\", resetting parser"; mIntoBeginLine = 0; } } } // uuencoding just shifts all 6-bit octets by 32 (SP/' '), except NUL, // which gets mapped to 0x60 static inline uchar uuDecode(uchar c) { return (c - ' ') // undo shift and & 0x3F; // map 0x40 (0x60-' ') to 0... } bool UUDecoder::decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) { // First, check whether we still need to find the "begin" line: if (!mSawBegin || mIntoBeginLine != 0) { searchForBegin(scursor, send); } else if (mSawEnd) { // or if we are past the end line: scursor = send; // do nothing anymore... return true; } while (dcursor != dend && scursor != send) { uchar ch = *scursor++; uchar value; // Check whether we need to look for the "end" line: if (mIntoEndLine > 0) { static const char end[] = "end"; static const uint endLength = 3; if (ch == end[mIntoEndLine]) { ++mIntoEndLine; if (mIntoEndLine == endLength) { mSawEnd = true; scursor = send; // shortcut to the end return true; } continue; } else { // qWarning() << "UUDecoder: invalid line octet count looks like \"end\" (mIntoEndLine =" // << mIntoEndLine << ")!"; mIntoEndLine = 0; // fall through... } } // Normal parsing: // The first char of a line is an encoding of the length of the // current line. We simply ignore it: if (mLastWasCRLF) { // reset char-per-line counter: mLastWasCRLF = false; mCurrentOctetCount = 0; // try to decode the chars-on-this-line announcement: if (ch == 'e') { // maybe the beginning of the "end"? ;-) mIntoEndLine = 1; } else if (ch > 0x60) { // ### invalid line length char: what shall we do?? } else if (ch > ' ') { mAnnouncedOctetCount = uuDecode(ch); } else if (ch == '\n') { mLastWasCRLF = true; // oops, empty line } continue; } // try converting ch to a 6-bit value: if (ch > 0x60) { continue; // invalid char } else if (ch > ' ') { value = uuDecode(ch); } else if (ch == '\n') { // line end mLastWasCRLF = true; continue; } else { continue; } // add the new bits to the output stream and flush full octets: switch (mStepNo) { case 0: mOutbits = value << 2; break; case 1: if (mCurrentOctetCount < mAnnouncedOctetCount) { *dcursor++ = (char)(mOutbits | value >> 4); } ++mCurrentOctetCount; mOutbits = value << 4; break; case 2: if (mCurrentOctetCount < mAnnouncedOctetCount) { *dcursor++ = (char)(mOutbits | value >> 2); } ++mCurrentOctetCount; mOutbits = value << 6; break; case 3: if (mCurrentOctetCount < mAnnouncedOctetCount) { *dcursor++ = (char)(mOutbits | value); } ++mCurrentOctetCount; mOutbits = 0; break; default: assert(0); } mStepNo = (mStepNo + 1) % 4; // check whether we ran over the announced octet count for this line: if (mCurrentOctetCount == mAnnouncedOctetCount + 1) { // qWarning() // << "UUDecoder: mismatch between announced (" // << mAnnouncedOctetCount << ") and actual line octet count!"; } } // return false when caller should call us again: return scursor == send; } // UUDecoder::decode() } // namespace KCodecs