//<copyright>
//
// Copyright (c) 1996
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>

//<file>
//
// Name:    $RCSfile: codec.C,v $
//
// Purpose: Interface for common class CoDec.
//
// Created: 15 Jul 96 Thomas Starlinger
//
// $Id: codec.C,v 1.1 1997/02/05 11:00:02 bmarsch Exp $
//
// Description: Contains, retrieves and maintains all necessary information
//              for coding in different systems (uucoding, base64, quoted printable, ...)
//   
//</file>
//
// $Log: codec.C,v $
// Revision 1.1  1997/02/05 11:00:02  bmarsch
// Initial revision
//

#include "codec.h"
#include "progressb.h"

#include <stdio.h>


// *************************  B64 Tables & Constants.  *************************

const char CoDec::B64_PaddingChar = '=';
const char* CoDec::B64_EncodingTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                       "abcdefghijklmnopqrstuvwxyz"
                                       "0123456789+/";
char CoDec::B64_DecodingTable[128] = "X";

const int CoDec::PRGIND_STEP = 20;

char* CoDec::EncodingType[CoDec::CODEC_CONST_COUNT] = {
  "encode",
    "uuencode",
    "base64",
    "quoted-printable",
  "decode",
    "uudecode",
    "b64decode",
    "qpdecode"
};

// ****************************  Con/Destructors  ******************************

CoDec::CoDec(int m, ProgressBase* p, float i)
{
  // Build static base64 decoder table (only once per start => static).
  if (B64_DecodingTable[0] == 'X') {
    int j=0;
    for (j=0; j<128; j++) B64_DecodingTable[j]=0;
    for (j=0; j<64; j++) B64_DecodingTable[(int)B64_EncodingTable[j]]=j;
  }

  progress_=p;
  prg_inc_=i;
  clear_=nil;
  code_=nil;
  clear_len_=-1;
  code_len_=-1;
  clear_alloc_=-1;
  code_alloc_=-1;
  buffer_len_=-1;

  encoding_method_=m;
  is_encoded_=0;
}

CoDec::CoDec(const char* t, long l, int m, ProgressBase* p, float i)
{
  // Build static base64 decoder table (only once per start => static).
  if (B64_DecodingTable[0]=='X') {
    int j;
    for (j=0;j<128;j++) B64_DecodingTable[j]=0;
    for (j=0;j<64;j++) B64_DecodingTable[(int)B64_EncodingTable[j]]=j;
  }

  encoding_method_=m;
  progress_=p;
  prg_inc_=i;
  clear_=nil;
  code_=nil;
  clear_len_=-1;
  code_len_=-1;
  clear_alloc_=-1;
  code_alloc_=-1;
  buffer_len_=-1;

  setBuffer(t,l);
  codeBuffer();
}

CoDec::~CoDec()
{
  if (code_alloc_>0) delete code_;
  if (clear_alloc_>0) delete clear_;
}

// ************************  Miscellaneous functions  **************************

// Used to set incoming buffer.
void CoDec::setBuffer(const char *t, long l) {
  // Set encoding flag.
  is_encoded_=0;

  if (encoding_method_<DECODE) {
    if (!t) {
      // No text so we use the internal buffer.
      clear_=(unsigned char *)buffer_;
      clear_len_=buffer_len_;
      clear_alloc_=-1;
    } else {
      // Text -> copy address and get length.
      if (clear_alloc_>0) {
	delete clear_;
	clear_alloc_=-1;
      }
      clear_=(unsigned char *)t;
      if (l>-1)
	clear_len_=l;
      else
	clear_len_=::strlen((const char*)clear_);
      buffer_len_=0;
    }
  } else if (encoding_method_>DECODE) {
    if (!t) {
      // No text so we use the internal buffer.
      code_=(unsigned char *)buffer_;
      code_len_=buffer_len_;
      code_alloc_=-1;
    } else {
      // Text -> copy address and get length.
      if (code_alloc_>0) {
	delete code_;
	code_alloc_=-1;
      }
      code_=(unsigned char *)t;
      if (l>-1)
	code_len_=l;
      else
	code_len_=::strlen((const char*)code_);
      buffer_len_=0;
    }
  } else
    is_encoded_=1;

  // Tranlate the buffer contents.
  codeBuffer();
}

// Select correct encoding method.
void CoDec::codeBuffer() {
  // Buffer is already encoded.
  if (is_encoded_) return;

  switch (encoding_method_) {
  case UUENCODE:
    is_encoded_=uu_encode();
    break;
  case UUDECODE:
    is_encoded_=uu_decode();
    break;
  case B64ENCODE:
    is_encoded_=b64_encode();
    break;
  case B64DECODE:
    is_encoded_=b64_decode();
    break;
  case QPENCODE:
    is_encoded_=qp_encode();
    break;
  case QPDECODE:
    is_encoded_=qp_decode();
    break;
  }
}

char *CoDec::getText(int m) {
  // Code or clear text.
  int mTmp=m;

  // Set to encoding method.
  if (m<0) 
    mTmp=encoding_method_;
  else
    if (encoding_method_!=mTmp) {
      encoding_method_=mTmp;
      codeBuffer();
    }

  if (mTmp<DECODE) {
    return (char *)code_;
  } else
    return (char *)clear_;
}

// *******************************  UUEN/DECODE  *******************************

// Encode the incoming RString and 
int CoDec::uu_encode() {
  int i, bytes, step=0;
  unsigned char tmp[4];

  // Characters in our inbuffer.
  int toWrite = clear_len_;

  // Thats not beautiful but we don't write, so it's ok (If you change something,
  // keep an eye on the possibility of writing this string).
  unsigned char *p=clear_;

  // Allocate memory (we build lines of 60 characters starting with a count byte and a 
  // ending newline. The coding generates 4 bytes from 24 bits [=3 bytes]).
  if (code_alloc_<((toWrite/45+1)*62+4)) {
    // Was there already space allocated?
    if (code_alloc_>0) delete code_;
    code_alloc_=(toWrite/45+1)*62+4;
    code_=(unsigned char *)new char[code_alloc_];
  }
  unsigned char *chPtr=code_;

  if (!toWrite) return 0;

  // Stepping through the string bytewise is dirty pointer hacking,
  // but it's the fastest methode and we (hopefully) know what we are doing.
  while (toWrite>0) {
    // Calculate bytes for this line.
    bytes=(toWrite>45)?45:toWrite;

    // A little bit messy, but ...
    *chPtr++=(char)(bytes + 0x20);

    for (i=0;i<bytes;i+=3) {
      if (i+2<bytes) {
	uu_encode_(p+i,chPtr);
	chPtr+=4;
      } else {
	// There are less then 3 bytes remaining, we fill with \000.
	tmp[0] = *(p+i);
	tmp[1] = (i+1<toWrite) ? *(p+i+1) : '\0';
	tmp[2] = '\0';
	uu_encode_(tmp,chPtr);
	chPtr+=4;
      }
    }
    p+=bytes;
    toWrite-=bytes;
    *chPtr++='\n';

    // Correct progress indicator.
    step+=bytes;
    if (progress_) {
      if (progress_->stop())
        return 0;
      if (step>(clear_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=step%bytes;
      }
    }
  }

  // These are delimiters for uuen/decode.
  if (buffer_len_==0 || clear_len_<CoDec::MAX_BUFFER) {
    *chPtr++='`';
    *chPtr++='\n';
    *chPtr++=0;
  }
  
  // Calculate length of written code.
  code_len_=chPtr-code_;

  return 1;
}

// Decode the incoming RString and 
int CoDec::uu_decode() {
  int i, bytes, step=0;

  // Thats not beautiful but we don't write, so it's ok (If you change something,
  // keep an eye on the possibility of writing this string).
  const unsigned char *p=(const unsigned char*)code_;

  // Calculate the bytes to read (subtract 2 for the last line "`\n").
  const unsigned char *toRead=p+code_len_-2;

  if (clear_alloc_<(code_len_*3/4)) {
    // Was there already space allocated?
    if (clear_alloc_>0) delete clear_;
    clear_alloc_=code_len_*3/4;
    clear_=(unsigned char *)new char[clear_alloc_];
  }
  unsigned char *chPtr=clear_;

  if (toRead<=p) return 0;

  // Stepping through the string bytewise is dirty pointer hacking,
  // but it's the fastest methode and we (hopefully) know what we are doing.
  clear_len_=0;
  while (p<toRead) {
    // Calculate bytes for this line.
    bytes=(*p++)-0x20;

    for (i=0;i<bytes;i+=3) {
      chPtr+=uu_decode_(p,chPtr);
      p+=4;
    }
    p++;
    // Calculate length of written text.
    clear_len_+=bytes;

    // Correct progress indicator.
    step+=bytes*3/4;
    if (progress_) {
      if (progress_->stop()) return 0;
      if (step>(code_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=step%(bytes*3/4);
      }
    }
  }

  return 1;
}

// The actual encoding routine (3 bytes in, 4 bytes out, NO \0 TERMINATION)
int CoDec::uu_encode_(const unsigned char* in, unsigned char* outbuf)
{
  outbuf[0] = (in[0] >> 2);
  outbuf[1] = ((in[0] << 4) & 0x30) | in[1] >> 4;
  outbuf[2] = ((in[1] << 2) & 0x3C) | in[2] >> 6;
  outbuf[3] = (in[2] & 0x3F);

  for (int i=0;i<4;i++)
    if (!outbuf[i]) 
      outbuf[i]='`';
    else
      outbuf[i]+=0x20;
  
  // Return number of valid outout bytes (only for compatibility with
  // other coding routines)
  return 4;
}


// The actual decoding routine (4 bytes in, 3 bytes out, NO \0 TERMINATION)
int CoDec::uu_decode_(const unsigned char* in, unsigned char* outbuf) {
  char c[4];

  c[0]=in[0];
  c[1]=in[1];
  c[2]=in[2];
  c[3]=in[3];

  for (int i=0;i<4;i++) {
    if (c[i]==0x20)
      c[i]='`';
    else
      c[i]-=0x20;
  }

  outbuf[0] = (c[0] << 2) | ((c[1] & 0x30) >> 4); 
  outbuf[1] = (c[1] << 4) | ((c[2] >> 2) & 0x0F);
  outbuf[2] = (c[2] << 6) | (c[3] & 0x3F);

  // Return number of valid outout bytes (only for compatibility with
  // other coding routines)
  return 3;
}

// ****************************  base64 EN/DECODE  *****************************

int CoDec::b64_encode() {
  // Clear text array pointer.
  const unsigned char *ptrClear=(const unsigned char *)clear_;
  unsigned char tmp[3];
  int i, step=0;

  // No clear text ?
  if (clear_len_<0) return 0;

  // Do we need to create space for encoded text?
  if (code_alloc_<((clear_len_/54+1)*73)) {
    // Was there already space allocated?
    if (code_alloc_>0) delete code_;
    code_alloc_=((clear_len_/54+1)*73);
    code_=(unsigned char *)new char[code_alloc_];
  }
  unsigned char *ptrCode=code_;
  
  // Step through mem in tripples.
  code_len_=0;
  for(i=0;i<(clear_len_/3);i++) {
    b64_encode_(ptrClear,ptrCode);
    ptrClear+=3;
    ptrCode+=4;
    // 54 clear text characters (=72 code) per line.
    if (i && !((i+1)%18)) *ptrCode++='\n';

    // Correct progress indicator.
    step+=3;
    if (progress_) {
      if (progress_->stop()) return 0;
      if (step>(clear_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=step%3;
      }
    }
  }

  // Extra treatment for non tripple end sequence.
  tmp[2]=tmp[1]=tmp[0]=0;
  if ((clear_len_%3)==2) {
    // ... ending in double sequence (-> one padchar).
    tmp[1]=*(ptrClear+1);
    tmp[0]=*ptrClear;
    b64_encode_((const unsigned char *)tmp,ptrCode);
    ptrCode+=3;
    *ptrCode++=B64_PaddingChar;
  } else if ((clear_len_%3)==1) {
    // ... ending in a single byte (-> two padchars);
    tmp[0]=*ptrClear;
    b64_encode_((const unsigned char *)tmp,ptrCode);
    ptrCode+=2;
    *ptrCode++=B64_PaddingChar;
    *ptrCode++=B64_PaddingChar;
  }
  *ptrCode++='\n';
  *ptrCode++=0;

  code_len_=ptrCode-code_;

  return 1;
}

int CoDec::b64_decode() {
  // Clear text array pointer.
  const unsigned char *ptrCode=(const unsigned char *)code_;
  int step=0;

  // No clear text ?
  if (code_len_<0) return 0;

  // Do we need to create space for encoded text?
  if (clear_alloc_<((code_len_/73+1)*54)) {
    // Was there already space allocated?
    if (clear_alloc_>0) delete clear_;
    clear_alloc_=((code_len_/73+1)*54);
    clear_=(unsigned char *)new char[clear_alloc_];
  }
  unsigned char *ptrClear=clear_;
  
  while(ptrCode<(code_+code_len_)) {
    ptrClear+=b64_decode_(ptrCode,ptrClear);
    ptrCode+=4;
    if (*ptrCode=='\n') ptrCode++;

    // Correct progress indicator.
    step+=4;
    if (progress_) {
      if (progress_->stop()) return 0;
      if (step>(code_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=step%4;
      }
    }
  }
  clear_len_=ptrClear-clear_;

  return 1;
}

// Check if encountered character "is" base64.
inline int CoDec::checkB64(char c) {
  return ((c>='A' && c<='Z') ||
	  (c>='a' && c<='z') ||
	  (c>='0' && c<='9') ||
	  c=='/' || c=='+' || c=='=');
}

int CoDec::b64_encode_(const unsigned char* in, unsigned char* outbuf) {
  outbuf[0] = B64_EncodingTable[(int)(in[0] >> 2)];
  outbuf[1] = B64_EncodingTable[(int)(((in[0] << 4) & 0x30) | in[1] >> 4)];
  outbuf[2] = B64_EncodingTable[(int)(((in[1] << 2) & 0x3C) | in[2] >> 6)];
  outbuf[3] = B64_EncodingTable[(int)(in[2] & 0x3F)];

  // Return number of valid outout bytes (only for compatibility with
  // other coding routines)
  return 4;
}

int CoDec::b64_decode_(const unsigned char* in, unsigned char* outbuf) {
  char c[4];
  int rc=3;

  if (!checkB64(in[0]) || !checkB64(in[1]) || !checkB64(in[2]) || !checkB64(in[3])) {
    // error in quadruple: issue warning ?!?!?!?.
  }

  for (int i=0;i<4;i++)
    if (in[i]==B64_PaddingChar) {
      c[i]=0;
      rc--;
    } else
      c[i]=in[i];

  outbuf[0] = (B64_DecodingTable[(int)c[0]] << 2) | ((B64_DecodingTable[(int)c[1]] & 0x30) >> 4); 
  outbuf[1] = (B64_DecodingTable[(int)c[1]] << 4) | ((B64_DecodingTable[(int)c[2]] >> 2) & 0x0F);
  outbuf[2] = (B64_DecodingTable[(int)c[2]] << 6) | (B64_DecodingTable[(int)c[3]] & 0x3F);

  // Return number of valid outout bytes.
  return rc;
}


// ***********************  Quoted-Printable EN/DECODE  ************************

int CoDec::qp_encode() {
  // Clear text array pointer.
  unsigned char *ptrClear=(unsigned char *)clear_;
  int step=0;
  int cnt=0;
  unsigned char *lastClear=0, *lastCode=0;

  // No clear text ?
  if (clear_len_<0) return 0;

  // Do we need to create space for encoded text?
  if (code_alloc_<(clear_len_*3/2)) {
    // Was there already space allocated?
    if (code_alloc_>0) delete code_;
    code_alloc_=clear_len_*3/2;
    code_=(unsigned char *)new char[code_alloc_];
  }
  unsigned char *ptrCode=(unsigned char *)code_;

  // Use algorithm and rules depicted in (RFC 1521, ch. 5.1)
  while(ptrClear<(clear_+clear_len_)) {
    // We might need this position for "soft" line breaks.
    if (*ptrClear==' ' || *ptrClear=='\t') {
      lastCode=ptrCode;
      lastClear=ptrClear;
    } else if (*ptrClear=='\n') {
      lastCode=lastClear=0;
      cnt=0;
    }
    // If the buffer is to small -> enlarge it.
    if (cnt>(code_alloc_-3)) {
      // Take care for shifted pointers.
      int diff=ptrCode-code_;
      code_alloc_=resizeBuffer((char *)code_,code_alloc_,code_alloc_/2);
      ptrCode=code_+diff;
    }
    // Copy or encode (RFC 1521, ch. 5.1, Rule 1) 
    cnt+=qp_encode_(ptrClear++, ptrCode);

    // If we got more then 74 characters (76 are allowed but we need
    // one for the line break and we might need one for an end pad)
    // (RFC 1521, ch. 5.1, Rule 5 "soft" line breaks)
    if (cnt>71) {
      if (lastCode!=0 && lastClear!=0) {
	ptrCode=lastCode;
	ptrClear=lastClear;
	lastCode=lastClear=0;
      }
      *ptrCode++='=';
      *ptrCode++='\n';
      cnt=0;
    }

    // Correct progress indicator.
    step++;
    if (progress_) {
      if (progress_->stop()) return 0;
      if (step>(clear_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=0;
      }
    }
  }

  // Calculate length of code.
  code_len_=ptrCode-code_;

  return 1;
}

int CoDec::qp_decode() {
  // Clear text array pointer.
  unsigned char *ptrCode=(unsigned char *)code_;
  int step=0;

  // No clear text ?
  if (code_len_<0) return 0;

  // Do we need to create space for encoded text?
  if (clear_alloc_<code_len_) {
    // Was there already space allocated?
    if (clear_alloc_>0) delete clear_;
    clear_alloc_=code_len_;
    clear_=(unsigned char *)new char[clear_alloc_];
  }
  unsigned char *ptrClear=(unsigned char *)clear_;

  // Use algorithm and rules depicted in (RFC 1521, ch. 5.1)
  while(ptrCode<(code_+code_len_)) {
    qp_decode_(ptrCode,ptrClear);
    ptrCode++;

    // Correct progress indicator.
    step++;
    if (progress_) {
      if (progress_->stop()) return 0;
      if (step>(code_len_/PRGIND_STEP)) {
	progress_->increase(prg_inc_/PRGIND_STEP);
	step=0;
      }
    }
  }

  clear_len_=ptrClear-clear_;

  return 1;
}

int CoDec::qp_encode_(const unsigned char* c, unsigned char *&ptr) {
  // A valid character (Whitespace (space or tab) represents a regular
  // char only when followed by a non newline character).
  if (charQPText(*c) || ((*c==' ' || *c=='\t') && *(c+1)!='\n')) {
    *ptr++=*c;
    return 1;
  }

  // Create a hex rep from the character.
  *ptr++='=';
  *ptr++=((*c/16)<10?(*c/16)+48:(*c/16)+55);
  *ptr++=((*c%16)<10?(*c%16)+48:(*c%16)+55);

  return 3;
}

int CoDec::qp_decode_(unsigned char* &c, unsigned char *&ptr) {
  if (*c=='=') {
    // This line break was "soft" (inserted be coder) -> ignore.
    if (*(c+1)=='\n') { 
      c++;
      return 0;
    }

    *ptr=0;
    for (int i=16;i>0;i/=16) {
      // Like RFC1521 says: "Uppercase letters must be used when sending
      // hexadecimal data, though a ROBUST implementation may choose to
      // recognize lowercase letters on receipt."
      c++;
      if (*c>='A' && *c<='F')
	*ptr+=(*c-55)*i;
      else if (*c>='a' && *c<='f')
	*ptr+=(*c-87)*i;
      else 
	*ptr+=(*c-48)*i;
    }

    ptr++;
    return 1;
  }

  // Simple character -> copy.
  *ptr++=*c;
  return 1;
}

// Create new and larger (or smaller) chunk of mem.
long CoDec::resizeBuffer(char *t, long s, long e) {
  if (t) {
    char *tmp=new char[s+e];
    ::memcpy(tmp, t, s);
    delete t;
    t=tmp;
  }

  return s;
}

// ***************************  IOStream operators  ****************************

// Friend operators:
istream & operator >> (istream &i, CoDec &c) {
  int buflen=CoDec::MAX_BUFFER;

  while (buflen>0 && i.good()) {
    i.read((c.buffer_+CoDec::MAX_BUFFER-buflen),buflen);
    buflen-=i.gcount();
  }
  c.buffer_len_=CoDec::MAX_BUFFER-buflen;

  c.setBuffer(NULL);
  c.codeBuffer();

  return i;
}

ostream & operator << (ostream &o, const CoDec &c) {
  if (c.encoding_method_<CoDec::DECODE)
    o.write(c.code_,c.code_len_);
  else
    o.write(c.clear_,c.clear_len_);

  return o;
}
