/*  -*- c++ -*-
    kmime_codec_uuencode.cpp

    KMime, the KDE Internet mail/usenet news message library.
    Copyright (c) 2002 Marc Mutz <mutz@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/
/**
  @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 \<mutz@kde.org\>
*/

#include "kmime_codec_uuencode.h"

#include <kdebug.h>

#include <cassert>

using namespace KMime;

namespace KMime {

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( bool withCRLF=false )
      : Decoder( withCRLF ), mStepNo( 0 ),
        mAnnouncedOctetCount( 0 ), mCurrentOctetCount( 0 ),
        mOutbits( 0 ), mLastWasCRLF( true ),
        mSawBegin( false ), mIntoBeginLine( 0 ),
        mSawEnd( false ), mIntoEndLine( 0 ) {}

  public:
    virtual ~UUDecoder() {}

    bool decode( const char* &scursor, const char * const send,
                 char* &dcursor, const char * const dend );
    // ### really needs no finishing???
    bool finish( char* &dcursor, const char * const dend )
    { Q_UNUSED( dcursor ); Q_UNUSED( dend ); return true; }
};

Encoder * UUCodec::makeEncoder( bool ) const
{
  return 0; // encoding not supported
}

Decoder * UUCodec::makeDecoder( bool withCRLF ) const
{
  return new UUDecoder( withCRLF );
}

/********************************************************/
/********************************************************/
/********************************************************/

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 {
      kWarning() << "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 {
        kWarning() << "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:
    kWarning( mCurrentOctetCount == mAnnouncedOctetCount + 1 )
      << "UUDecoder: mismatch between announced ("
      << mAnnouncedOctetCount << ") and actual line octet count!";

  }

  // return false when caller should call us again:
  return scursor == send;
} // UUDecoder::decode()

} // namespace KMime
