/*****************************************************************************
*  Copyright 2005 Alt-N Technologies, Ltd.
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  This code incorporates intellectual property owned by Yahoo! and licensed
*  pursuant to the Yahoo! DomainKeys Patent License Agreement.
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*
*  Changes done by ¢feh@fehcom.de obeying the above license
*
*****************************************************************************/
#include "dkimbase.h"

#include <string.h>

#include <algorithm>

#include "dkim.h"

using std::string;


CDKIMBase::CDKIMBase() :
    m_From(nullptr),
    m_Sender(nullptr),
    m_hTag(nullptr),
    m_hTagSize(0),
    m_hTagPos(0),
    m_Line(nullptr),
    m_LineSize(0),
    m_LinePos(0),
    m_InHeaders(true)
{}

CDKIMBase::~CDKIMBase()  // delete
{
  Free(m_Line);
  Free(m_From);
  Free(m_Sender);
  Free(m_hTag);
}

int CDKIMBase::Init(void)
{
  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// Alloc - allocate buffer
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::Alloc(char *& szBuffer, int nRequiredSize)
{
  szBuffer = new char[nRequiredSize];

  return (szBuffer == NULL) ? DKIM_OUT_OF_MEMORY : DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// ReAlloc - extend buffer if necessary, leaving room for future expansion
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::ReAlloc(char *& szBuffer, int& nBufferSize, int nRequiredSize)
{
  if (nRequiredSize > nBufferSize) {
    char *newp;
    int nNewSize = nRequiredSize + BUFFER_ALLOC_INCREMENT;

    if (Alloc(newp, nNewSize) == DKIM_SUCCESS) {
      if (szBuffer != NULL && nBufferSize > 0) {
        memcpy(newp, szBuffer, nBufferSize);
        delete[] szBuffer;
      }
      szBuffer = newp;
      nBufferSize = nNewSize;
    } else {
      return DKIM_OUT_OF_MEMORY;  // memory alloc error!
    }
  }

  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// Process - split buffers into lines without any CRs or LFs at the end.
//
////////////////////////////////////////////////////////////////////////////////
void CDKIMBase::Free(char *szBuffer)
{
  delete[] szBuffer;
}

////////////////////////////////////////////////////////////////////////////////
//
// Process - split buffers into lines without any CRs or LFs at the end.
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::Process(const char *szBuffer, int nBufLength, bool bEOF)
{
  const char *p = szBuffer;
  const char *e = szBuffer + nBufLength;

  while (p < e) {
    if (*p != '\n' || m_LinePos == 0 || m_Line[m_LinePos - 1] != '\r') {
      // add char to line
      if (m_LinePos >= m_LineSize) {
        int nRet = ReAlloc(m_Line, m_LineSize, m_LinePos + 1);
        if (nRet != DKIM_SUCCESS) return nRet;
      }
      m_Line[m_LinePos++] = *p;
    } else {
      // back up past the CR
      m_LinePos--;

      if (m_InHeaders) {
        // process header line
        if (m_LinePos == 0) {
          m_InHeaders = false;
          int Result = ProcessHeaders();
          if (Result != DKIM_SUCCESS) return Result;
        } else {
          // append the header to the headers list
          if (m_Line[0] != ' ' && m_Line[0] != '\t') {
            HeaderList.push_back(string(m_Line, m_LinePos));
            //            fprintf(stderr," dkimbase.cpp:Process:Input:  %s \n",m_Line);
          } else {
            if (!HeaderList.empty()) {
              HeaderList.back().append("\r\n", 2).append(m_Line, m_LinePos);
            } else {
              // no header to append to...
            }
          }
        }
      } else {
        // process body line
        int Result = ProcessBody(m_Line, m_LinePos, bEOF);
        if (Result != DKIM_SUCCESS) {
          m_LinePos = 0;
          return Result;
        }
      }

      m_LinePos = 0;
    }

    p++;
  }

  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// ProcessFinal - process leftovers if stopping before the body or mid-line
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::ProcessFinal(void)
{
  if (m_LinePos > 0) Process("\r\n", 2, true);

  if (m_InHeaders) {
    m_InHeaders = false;
    ProcessHeaders();
    /* type conversion should be safe as length is zero */
    ProcessBody((char *)"", 0, true);
  }

  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// ProcessHeaders - process the headers (to be implemented by derived class)
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::ProcessHeaders()
{
  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// ProcessBody - process body line (to be implemented by derived class)
//
////////////////////////////////////////////////////////////////////////////////
int CDKIMBase::ProcessBody(char *szBuffer, int nBufLength, bool bEOF)
{
  return DKIM_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
// RemoveSWSP - remove streaming white space from buffer/string inline
//
////////////////////////////////////////////////////////////////////////////////

struct isswsp {
  bool operator()(char ch)
  {
    return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
  }
};

void CDKIMBase::RemoveSWSP(char *szBuffer)
{
  *std::remove_if(szBuffer, szBuffer + strlen(szBuffer), isswsp()) = '\0';
}

void CDKIMBase::RemoveSWSP(char *pBuffer, int& nBufLength)
{
  nBufLength = std::remove_if(pBuffer, pBuffer + nBufLength, isswsp()) - pBuffer;
}

void CDKIMBase::RemoveSWSP(string& sBuffer)
{
  sBuffer.erase(std::remove_if(sBuffer.begin(), sBuffer.end(), isswsp()), sBuffer.end());
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// CompressSWSP - compress streaming white space into single spaces from buffer/string inline
//
//////////////////////////////////////////////////////////////////////////////////////////

void CDKIMBase::CompressSWSP(char *pBuffer, int& nBufLength)
{
  char *pSrc = pBuffer;
  char *pDst = pBuffer;
  char *pEnd = pBuffer + nBufLength;

  while (pSrc != pEnd) {
    if (isswsp()(*pSrc)) {

      do {
        ++pSrc;
      } while (pSrc != pEnd && isswsp()(*pSrc));

      if (pSrc == pEnd) break;

      *pDst++ = ' ';
    }

    *pDst++ = *pSrc++;
  }

  nBufLength = pDst - pBuffer;
}

void CDKIMBase::CompressSWSP(string& sBuffer)
{
  string::iterator iSrc = sBuffer.begin();
  string::iterator iDst = sBuffer.begin();
  string::iterator iEnd = sBuffer.end();

  while (iSrc != iEnd) {
    if (isswsp()(*iSrc)) {

      do {
        ++iSrc;
      } while (iSrc != iEnd && isswsp()(*iSrc));

      if (iSrc == iEnd) break;

      *iDst++ = ' ';
    }

    *iDst++ = *iSrc++;
  }

  sBuffer.erase(iDst, iEnd);
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// RelaxHeader - relax a header field (lower case the name, remove swsp before and after :)
//
// modified 4/21/06 STB to remove white space before colon
//
//////////////////////////////////////////////////////////////////////////////////////////

string CDKIMBase::RelaxHeader(const string& sHeader)
{
  string sTemp = sHeader;

  CompressSWSP(sTemp);

  string::size_type cpos = sTemp.find(':');

  if (cpos == string::npos) {
    // no colon?!
  } else {
    // lower case the header field name
    for (unsigned i = 0; i < cpos; i++) {
      if (sTemp[i] >= 'A' && sTemp[i] <= 'Z') sTemp[i] += 'a' - 'A';
    }

    // remove the space after the :
    if (cpos + 1 < sTemp.length() && sTemp[cpos + 1] == ' ') sTemp.erase(cpos + 1, 1);

    // remove the space before the :
    if (cpos > 0 && sTemp[cpos - 1] == ' ') sTemp.erase(cpos - 1, 1);
  }

  return sTemp;
}