//=============================================================================
// File:       pop.cpp
// Contents:   Definitions for DwPopClient
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
//
// Copyright (c) 1996, 1997 Douglas W. Sauder
// All rights reserved.
//
// IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT,
// INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
// THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER
// HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
// BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
//=============================================================================

#define DW_IMPLEMENTATION

#include <mimelib/config.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <mimelib/pop.h>
#include <config.h>

#define POP_PORT 110
#define RECV_BUFFER_SIZE  8192
#define SEND_BUFFER_SIZE  1024

#if defined(DW_DEBUG_POP)
#  define DBG_POP_STMT(x) x
#else
#  define DBG_POP_STMT(x)
#endif


DwPopClient::DwPopClient()
{
    mSendBuffer = new char[SEND_BUFFER_SIZE];
    mRecvBuffer = new char[RECV_BUFFER_SIZE];
    mNumRecvBufferChars = 0;
    mRecvBufferPos = 0;
    mStatusCode = 0;
    mObserver = 0;
}


DwPopClient::~DwPopClient()
{
    if (mRecvBuffer) {
        delete [] mRecvBuffer;
        mRecvBuffer = 0;
    }
    if (mSendBuffer) {
        delete [] mSendBuffer;
        mSendBuffer = 0;
    }
}


int DwPopClient::Open(const char* aServer, DwUint16 aPort)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    int err = DwProtocolClient::Open(aServer, aPort);
    if (! err) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


DwObserver* DwPopClient::SetObserver(DwObserver* aObserver)
{
    DwObserver* obs = mObserver;
    mObserver = aObserver;
    return obs;
}


int DwPopClient::StatusCode() const
{
    return mStatusCode;
}


const DwString& DwPopClient::SingleLineResponse() const
{
    return mSingleLineResponse;
}


const DwString& DwPopClient::MultiLineResponse() const
{
    return mMultiLineResponse;
}


int DwPopClient::User(const char* aName)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdUser;
    strlcpy(mSendBuffer, "USER ", SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, aName, SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, "\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Pass(const char* aPasswd)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdPass;
    strlcpy(mSendBuffer, "PASS ", SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, aPasswd, SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, "\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Quit()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdQuit;
    strlcpy(mSendBuffer, "QUIT\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Stat()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdStat;
    strlcpy(mSendBuffer, "STAT\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::List()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdList;
    strlcpy(mSendBuffer, "LIST\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
        if (mStatusCode == '+') {
            PGetMultiLineResponse();
        }
    }
    return mStatusCode;
}


int DwPopClient::List(int aMsg)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdList;
    snprintf(mSendBuffer, SEND_BUFFER_SIZE, "LIST %d\r\n", aMsg);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Retr(int aMsg)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdRetr;
    snprintf(mSendBuffer, SEND_BUFFER_SIZE, "RETR %d\r\n", aMsg);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
        if (mStatusCode == '+') {
            PGetMultiLineResponse();
        }
    }
    return mStatusCode;
}


int DwPopClient::Dele(int aMsg)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdDele;
    snprintf(mSendBuffer, SEND_BUFFER_SIZE, "DELE %d\r\n", aMsg);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Noop()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdNoop;
    strlcpy(mSendBuffer, "NOOP\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Rset()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdRset;
    strlcpy(mSendBuffer, "RSET\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Last()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdRset;
    strlcpy(mSendBuffer, "LAST\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Apop(const char* aName, const char* aDigest)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdApop;
    strlcpy(mSendBuffer, "APOP ", SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, aName, SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, " ", SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, aDigest, SEND_BUFFER_SIZE);
    strlcat(mSendBuffer, "\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
    }
    return mStatusCode;
}


int DwPopClient::Top(int aMsg, int aNumLines)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdTop;
    snprintf(mSendBuffer, SEND_BUFFER_SIZE, "TOP %d %d\r\n", aMsg, aNumLines);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
        if (mStatusCode == '+') {
            PGetMultiLineResponse();
        }
    }
    return mStatusCode;
}


int DwPopClient::Uidl()
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdUidl;
    strlcpy(mSendBuffer, "UIDL\r\n", SEND_BUFFER_SIZE);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush;)
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
        if (mStatusCode == '+') {
            PGetMultiLineResponse();
        }
    }
    return mStatusCode;
}


int DwPopClient::Uidl(int aMsg)
{
    mStatusCode = 0;
    mSingleLineResponse = mMultiLineResponse = "";
    mLastCommand = kCmdUidl;
    snprintf(mSendBuffer, SEND_BUFFER_SIZE, "UIDL %d\r\n", aMsg);
    DBG_POP_STMT(cout << "C: " << mSendBuffer << flush);
    int bufferLen = strlen(mSendBuffer);
    int numSent = PSend(mSendBuffer, bufferLen);
    if (numSent == bufferLen) {
        PGetSingleLineResponse();
        if (mStatusCode == '+') {
            PGetMultiLineResponse();
        }
    }
    return mStatusCode;
}


void DwPopClient::PGetSingleLineResponse()
{
    mStatusCode = 0;
    mSingleLineResponse = "";
    char* ptr;
    int len;
    int err = PGetLine(&ptr, &len);
    if (! err) {
        mStatusCode = ptr[0];
        mSingleLineResponse.assign(ptr, len);
        DBG_POP_STMT(char buffer[256];)
        DBG_POP_STMT(strncpy(buffer, ptr, len);)
        DBG_POP_STMT(buffer[len] = 0;)
        DBG_POP_STMT(cout << "S: " << buffer;)
    }
}


void DwPopClient::PGetMultiLineResponse()
{
    mMultiLineResponse = "";

    // Get a line at a time until we get CR LF . CR LF

    while (1) {
        char* ptr;
        int len;
        int err = PGetLine(&ptr, &len);

        // Check for an error

        if (err) {
            mStatusCode = 0;
            return;
        }

        // Check for '.' on a line by itself, which indicates end of multiline
        // response

        if (len >= 3 && ptr[0] == '.' && ptr[1] == '\r' && ptr[2] == '\n') {
            break;
        }

        // Remove '.' at beginning of line

        if (*ptr == '.') ++ptr;

        // If an observer is assigned, notify it.
        // Implementation note: An observer is assumed to fetch the multiline
        // response one line at a time, therefore we assign to the string,
        // rather than append to it.

        if (mObserver) {
            mMultiLineResponse.assign(ptr, len);
            mObserver->Notify();
        }
        else {
            mMultiLineResponse.append(ptr, len);
        }
    }
}


int DwPopClient::PGetLine(char** aPtr, int* aLen)
{
    // Restore the saved state

    int startPos = mRecvBufferPos;
    int pos = mRecvBufferPos;
    int lastChar = -1;

    // Keep trying until we get a complete line, detect an error, or
    // determine that the connection has been closed

    int isEndOfLineFound = 0;
    while (1) {

        // Search buffer for end of line chars. Stop when we find them or when
        // we exhaust the buffer.

        while (pos < mNumRecvBufferChars) {
            if (lastChar == '\r' && mRecvBuffer[pos] == '\n') {
                isEndOfLineFound = 1;
                ++pos;
                break;
            }
            lastChar = mRecvBuffer[pos];
            ++pos;
        }
        if (isEndOfLineFound) {
            *aPtr = &mRecvBuffer[startPos];
            *aLen = pos - startPos;
            mRecvBufferPos = pos;
            return 0;
        }

        // If the buffer has no room, return without an error; otherwise,
        // replenish the buffer.

        // Implementation note: The standard does not allow long lines,
        // however, that does not mean that they won't occur.  The way
        // this function deals with long lines is to return a full buffer's
        // worth of characters as a line.  The next call to this function
        // will start where this call left off.  In essence, we have
        // *forced* a line break, but without putting in CR LF characters.

        if (startPos == 0 && pos == RECV_BUFFER_SIZE) {
            *aPtr = mRecvBuffer;
            *aLen = RECV_BUFFER_SIZE;
            mRecvBufferPos = pos;
            return 0;
        }
        memmove(mRecvBuffer, &mRecvBuffer[startPos],
            mNumRecvBufferChars-startPos);
        mNumRecvBufferChars -= startPos;
        mRecvBufferPos = mNumRecvBufferChars;
        int bufFreeSpace = RECV_BUFFER_SIZE - mRecvBufferPos;
        int n = PReceive(&mRecvBuffer[mRecvBufferPos], bufFreeSpace);
        if (n == 0) {
            // The connection has been closed or an error occurred
            return -1;
        }
        mNumRecvBufferChars += n;
        startPos = 0;
        pos = mRecvBufferPos;
    }
}