You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
10318 lines
393 KiB
10318 lines
393 KiB
//***************************************************************************
|
|
// IMAP4 Protocol Class Implementation (CImap4Agent)
|
|
// Written by Raymond Cheng, 3/21/96
|
|
//
|
|
// This class allows its callers to use IMAP4 client commands without having
|
|
// to parse incidental responses from the IMAP4 server (which may contain
|
|
// information unrelated to the original command). For instance, during a
|
|
// SEARCH command, the IMAP server may issue EXISTS and RECENT responses to
|
|
// indicate the arrival of new mail.
|
|
//
|
|
// The user of this class first creates a connection by calling
|
|
// Connect. It is the caller's responsibility to ensure that the
|
|
// connection is not severed due to inactivity (autologout). The caller
|
|
// can guard against this by periodically sending Noop's.
|
|
//***************************************************************************
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Includes
|
|
//---------------------------------------------------------------------------
|
|
#include "pch.hxx"
|
|
#include <iert.h>
|
|
#include "IMAP4.h"
|
|
#include "range.h"
|
|
#include "dllmain.h"
|
|
#include "resource.h"
|
|
#include "mimeole.h"
|
|
#include <shlwapi.h>
|
|
#include "strconst.h"
|
|
#include "demand.h"
|
|
|
|
// I chose the IInternetTransport from IIMAPTransport instead
|
|
// of CIxpBase, because I override some of CIxpBase's IInternetTransport
|
|
// implementations, and I want CImap4Agent's versions to take priority.
|
|
#define THIS_IInternetTransport ((IInternetTransport *) (IIMAPTransport *) this)
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Module Constants
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Module Constants
|
|
//---------------------------------------------------------------------------
|
|
// *** Stolen from msgout.cpp! Find out how we can SHARE ***
|
|
// Assert(FALSE); // Placeholder
|
|
// The following is used to allow us to output dates in IMAP-compliant fashion
|
|
static LPSTR lpszMonthsOfTheYear[] =
|
|
{
|
|
"Filler",
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
|
|
const int TAG_BUFSIZE = NUM_TAG_CHARS + 1;
|
|
const int MAX_RESOURCESTRING = 512;
|
|
|
|
|
|
// IMAP Stuff
|
|
const char cCOMMAND_CONTINUATION_PREFIX = '+';
|
|
const char cUNTAGGED_RESPONSE_PREFIX = '*';
|
|
const char cSPACE = ' ';
|
|
|
|
const char c_szIMAP_MSG_ANSWERED[] = "Answered";
|
|
const char c_szIMAP_MSG_FLAGGED[] = "Flagged";
|
|
const char c_szIMAP_MSG_DELETED[] = "Deleted";
|
|
const char c_szIMAP_MSG_DRAFT[] = "Draft";
|
|
const char c_szIMAP_MSG_SEEN[] = "Seen";
|
|
|
|
const char c_szDONE[] = "DONE\r\n";
|
|
|
|
|
|
// *** Unless you can guarantee that g_szSPACE and c_szCRLF stay
|
|
// *** US-ASCII, I'll use these. Assert(FALSE); (placeholder)
|
|
// const char c_szCRLF[] = "\r\n";
|
|
// const char g_szSpace[] = " ";
|
|
|
|
const boolean TAGGED = TRUE;
|
|
const boolean UNTAGGED = FALSE;
|
|
const BOOL fFREE_BODY_TAG = TRUE;
|
|
const BOOL fDONT_FREE_BODY_TAG = FALSE;
|
|
const BOOL tamNEXT_AUTH_METHOD = TRUE;
|
|
const BOOL tamCURRENT_AUTH_METHOD = FALSE;
|
|
const BOOL rcASTRING_ARG = TRUE;
|
|
const BOOL rcNOT_ASTRING_ARG = FALSE;
|
|
|
|
// For use with SendCmdLine
|
|
const DWORD sclAPPEND_TO_END = 0x00000000; // This option happens by default
|
|
const DWORD sclINSERT_BEFORE_PAUSE = 0x00000001;
|
|
const DWORD sclAPPEND_CRLF = 0x00000002;
|
|
|
|
const DWORD dwLITERAL_THRESHOLD = 128; // On the conservative side
|
|
|
|
const MBOX_MSGCOUNT mcMsgCount_INIT = {FALSE, 0L, FALSE, 0L, FALSE, 0L};
|
|
const FETCH_BODY_PART FetchBodyPart_INIT = {0, NULL, 0, 0, 0, FALSE, NULL, 0, 0};
|
|
const AUTH_STATUS AuthStatus_INIT = {asUNINITIALIZED, FALSE, 0, 0, {0}, {0}, NULL, 0};
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Functions
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: CImap4Agent (Constructor)
|
|
//***************************************************************************
|
|
CImap4Agent::CImap4Agent (void) : CIxpBase(IXP_IMAP)
|
|
{
|
|
DOUT("CImap4Agent - CONSTRUCTOR");
|
|
|
|
// Initialize module variables
|
|
m_ssServerState = ssNotConnected;
|
|
m_dwCapabilityFlags = 0;
|
|
*m_szLastResponseText = '\0';
|
|
DllAddRef();
|
|
m_lRefCount = 1;
|
|
m_pCBHandler = NULL;
|
|
m_irsState = irsUNINITIALIZED;
|
|
m_bFreeToSend = TRUE;
|
|
m_fIDLE = FALSE;
|
|
m_ilqRecvQueue = ImapLinefragQueue_INIT;
|
|
|
|
InitializeCriticalSection(&m_csTag);
|
|
InitializeCriticalSection(&m_csSendQueue);
|
|
InitializeCriticalSection(&m_csPendingList);
|
|
|
|
m_pilfLiteralInProgress = NULL;
|
|
m_dwLiteralInProgressBytesLeft = 0;
|
|
m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT;
|
|
m_dwAppendStreamUploaded = 0;
|
|
m_dwAppendStreamTotal = 0;
|
|
|
|
m_bCurrentMboxReadOnly = TRUE;
|
|
|
|
m_piciSendQueue = NULL;
|
|
m_piciPendingList = NULL;
|
|
m_piciCmdInSending = NULL;
|
|
|
|
m_pInternational = NULL;
|
|
m_dwTranslateMboxFlags = IMAP_MBOXXLATE_DEFAULT;
|
|
m_uiDefaultCP = GetACP(); // Must be default CP because we shipped like this
|
|
m_asAuthStatus = AuthStatus_INIT;
|
|
|
|
m_pdwMsgSeqNumToUID = NULL;
|
|
m_dwSizeOfMsgSeqNumToUID = 0;
|
|
m_dwHighestMsgSeqNum = 0;
|
|
|
|
m_dwFetchFlags = 0;
|
|
} // CImap4Agent
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ~CImap4Agent (Destructor)
|
|
//***************************************************************************
|
|
CImap4Agent::~CImap4Agent(void)
|
|
{
|
|
DOUT("CImap4Agent - DESTRUCTOR");
|
|
|
|
Assert(0 == m_lRefCount);
|
|
|
|
DropConnection(); // Ignore return result, since there's nothing we can do
|
|
FreeAllData(E_FAIL); // General failure result, if cmds pending while destructor invoked
|
|
|
|
DeleteCriticalSection(&m_csTag);
|
|
DeleteCriticalSection(&m_csSendQueue);
|
|
DeleteCriticalSection(&m_csPendingList);
|
|
|
|
if (NULL != m_pInternational)
|
|
m_pInternational->Release();
|
|
|
|
if (NULL != m_pCBHandler)
|
|
m_pCBHandler->Release();
|
|
|
|
DllRelease();
|
|
} // ~CImap4Agent
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: QueryInterface
|
|
//
|
|
// Purpose:
|
|
// Read the Win32SDK OLE Programming References (Interfaces) about the
|
|
// IUnknown::QueryInterface function for details. This function returns a
|
|
// pointer to the requested interface.
|
|
//
|
|
// Arguments:
|
|
// REFIID iid [in] - an IID identifying the interface to return.
|
|
// void **ppvObject [out] - if successful, this function returns a pointer
|
|
// to the requested interface in this argument.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::QueryInterface(REFIID iid, void **ppvObject)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppvObject);
|
|
|
|
// Init variables, check the arguments
|
|
hrResult = E_NOINTERFACE;
|
|
if (NULL == ppvObject) {
|
|
hrResult = E_INVALIDARG;
|
|
goto exit;
|
|
}
|
|
|
|
*ppvObject = NULL;
|
|
|
|
// Find a ptr to the interface
|
|
if (IID_IUnknown == iid) {
|
|
// Choose the IIMAPTransport path to IUnknown over the other 3 paths
|
|
// (all through CIxpBase) because this guarantees that CImap4Agent
|
|
// provides the IUnknown implementation.
|
|
*ppvObject = (IUnknown *) (IIMAPTransport *) this;
|
|
((IUnknown *) (IIMAPTransport *) this)->AddRef();
|
|
}
|
|
|
|
if (IID_IInternetTransport == iid) {
|
|
*ppvObject = THIS_IInternetTransport;
|
|
(THIS_IInternetTransport)->AddRef();
|
|
}
|
|
|
|
if (IID_IIMAPTransport == iid) {
|
|
*ppvObject = (IIMAPTransport *) this;
|
|
((IIMAPTransport *) this)->AddRef();
|
|
}
|
|
|
|
if (IID_IIMAPTransport2 == iid) {
|
|
*ppvObject = (IIMAPTransport2 *) this;
|
|
((IIMAPTransport2 *) this)->AddRef();
|
|
}
|
|
|
|
// Return success if we managed to snag an interface
|
|
if (NULL != *ppvObject)
|
|
hrResult = S_OK;
|
|
|
|
exit:
|
|
return hrResult;
|
|
} // QueryInterface
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AddRef
|
|
//
|
|
// Purpose:
|
|
// This function should be called whenever someone makes a copy of a
|
|
// pointer to this object. It bumps the reference count so that we know
|
|
// there is one more pointer to this object, and thus we need one more
|
|
// release before we delete ourselves.
|
|
//
|
|
// Returns:
|
|
// A ULONG representing the current reference count. Although technically
|
|
// our reference count is signed, we should never return a negative number,
|
|
// anyways.
|
|
//***************************************************************************
|
|
ULONG STDMETHODCALLTYPE CImap4Agent::AddRef(void)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
m_lRefCount += 1;
|
|
|
|
DOUT ("CImap4Agent::AddRef, returned Ref Count=%ld", m_lRefCount);
|
|
return m_lRefCount;
|
|
} // AddRef
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Release
|
|
//
|
|
// Purpose:
|
|
// This function should be called when a pointer to this object is to
|
|
// go out of commission. It knocks the reference count down by one, and
|
|
// automatically deletes the object if we see that nobody has a pointer
|
|
// to this object.
|
|
//
|
|
// Returns:
|
|
// A ULONG representing the current reference count. Although technically
|
|
// our reference count is signed, we should never return a negative number,
|
|
// anyways.
|
|
//***************************************************************************
|
|
ULONG STDMETHODCALLTYPE CImap4Agent::Release(void)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
m_lRefCount -= 1;
|
|
DOUT("CImap4Agent::Release, returned Ref Count = %ld", m_lRefCount);
|
|
|
|
if (0 == m_lRefCount) {
|
|
delete this;
|
|
return 0;
|
|
}
|
|
else
|
|
return m_lRefCount;
|
|
} // Release
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: InitNew
|
|
//
|
|
// Purpose:
|
|
// This function initializes the CImap4Agent class. This function
|
|
// must be the next function called after instantiating the CImap4Agent class.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszLogFilePath [in] - path to a log file (where all input and
|
|
// output is logged), if the caller wishes to log IMAP transactions.
|
|
// IIMAPCallback *pCBHandler [in] - pointer to a IIMAPCallback object.
|
|
// This object allows the CImap4Agent class to report all IMAP response
|
|
// results to its user.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::InitNew(LPSTR pszLogFilePath, IIMAPCallback *pCBHandler)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(ssNotConnected == m_ssServerState);
|
|
Assert(irsUNINITIALIZED == m_irsState);
|
|
|
|
Assert(NULL != pCBHandler);
|
|
|
|
pCBHandler->AddRef();
|
|
m_pCBHandler = pCBHandler;
|
|
m_irsState = irsNOT_CONNECTED;
|
|
|
|
hrResult = MimeOleGetInternat(&m_pInternational);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
return CIxpBase::OnInitNew("IMAP", pszLogFilePath, FILE_SHARE_READ,
|
|
(ITransportCallback *)pCBHandler);
|
|
} // InitNew
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SetDefaultCBHandler
|
|
//
|
|
// Purpose: This function changes the current default IIMAPCallback handler
|
|
// to the given one.
|
|
//
|
|
// Arguments:
|
|
// IIMAPCallback *pCBHandler [in] - a pointer to the new callback handler.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::SetDefaultCBHandler(IIMAPCallback *pCBHandler)
|
|
{
|
|
Assert(NULL != pCBHandler);
|
|
if (NULL == pCBHandler)
|
|
return E_INVALIDARG;
|
|
|
|
if (NULL != m_pCBHandler)
|
|
m_pCBHandler->Release();
|
|
|
|
if (NULL != m_pCallback)
|
|
m_pCallback->Release();
|
|
|
|
m_pCBHandler = pCBHandler;
|
|
m_pCBHandler->AddRef();
|
|
m_pCallback = pCBHandler;
|
|
m_pCallback->AddRef();
|
|
return S_OK;
|
|
} // SetDefaultCBHandler
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SetWindow
|
|
//
|
|
// Purpose:
|
|
// This function creates the current window handle for async winsock process.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::SetWindow(void)
|
|
{
|
|
Assert(NULL != m_pSocket);
|
|
return m_pSocket->SetWindow();
|
|
} // SetWindow
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ResetWindow
|
|
//
|
|
// Purpose:
|
|
// This function closes the current window handle for async winsock process.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::ResetWindow(void)
|
|
{
|
|
Assert(NULL != m_pSocket);
|
|
return m_pSocket->ResetWindow();
|
|
} // ResetWindow
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Connect
|
|
//
|
|
// Purpose:
|
|
// This function is called to establish a connection with the IMAP server,
|
|
// get its capabilities, and to authenticate the user.
|
|
//
|
|
// Arguments:
|
|
// See explanation in imnxport.idl.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Connect(LPINETSERVER pInetServer,
|
|
boolean fAuthenticate,
|
|
boolean fCommandLogging)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(ssAuthenticated > m_ssServerState);
|
|
Assert(irsUNINITIALIZED < m_irsState);
|
|
|
|
// We do not accept all combinations of argument: the caller cannot
|
|
// perform his own authentication, and thus we MUST be responsible for
|
|
// this. Even if PREAUTH is expected, we expect fAuthenticate to be TRUE.
|
|
if (FALSE == fAuthenticate) {
|
|
AssertSz(FALSE, "Current IIMAPTransport interface requires that fAuthenticate be TRUE.");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Neither can we call the OnCommand callback
|
|
if (fCommandLogging) {
|
|
AssertSz(FALSE, "Current IIMAPTransport interface requires that fCommandLogging be FALSE.");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Does user want us to always prompt for his password? Prompt him here to avoid
|
|
// inactivity timeouts while the prompt is up. Do not prompt if password supplied.
|
|
if (ISFLAGSET(pInetServer->dwFlags, ISF_ALWAYSPROMPTFORPASSWORD) &&
|
|
'\0' == pInetServer->szPassword[0]) {
|
|
if (NULL != m_pCallback)
|
|
hrResult = m_pCallback->OnLogonPrompt(pInetServer, THIS_IInternetTransport);
|
|
|
|
if (NULL == m_pCallback || S_OK != hrResult)
|
|
return IXP_E_USER_CANCEL;
|
|
}
|
|
|
|
// If we reach this point, we need to establish a connection to IMAP server
|
|
Assert(ssNotConnected == m_ssServerState);
|
|
Assert(irsNOT_CONNECTED == m_irsState);
|
|
|
|
hrResult = CIxpBase::Connect(pInetServer, fAuthenticate, fCommandLogging);
|
|
if (SUCCEEDED(hrResult)) {
|
|
m_irsState = irsSVR_GREETING;
|
|
m_ssServerState = ssConnecting;
|
|
}
|
|
|
|
return hrResult;
|
|
} // Connect
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ReLoginUser
|
|
//
|
|
// Purpose:
|
|
// This function is called to re-attempt user authentication after a
|
|
// failed attempt. It calls ITransportCallback::OnLogonPrompt to allow
|
|
// the user to provide the correct logon information.
|
|
//***************************************************************************
|
|
void CImap4Agent::ReLoginUser(void)
|
|
{
|
|
HRESULT hrResult;
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
|
|
|
|
if (NULL == m_pCallback) {
|
|
// We can't do a damned thing, drop connection (this can happen due to HandsOffCallback)
|
|
DropConnection();
|
|
return;
|
|
}
|
|
|
|
// Init variables
|
|
szFailureText[0] = '\0';
|
|
|
|
// First, put us in IXP_AUTHRETRY mode so that OnStatus is not called
|
|
// for changes to the connection status
|
|
OnStatus(IXP_AUTHRETRY);
|
|
|
|
// OK, connection status is no longer being reported to the user
|
|
// Ask the user for his stinking password
|
|
hrResult = m_pCallback->OnLogonPrompt(&m_rServer, THIS_IInternetTransport);
|
|
if (FAILED(hrResult) || S_FALSE == hrResult) {
|
|
AssertSz(SUCCEEDED(hrResult), "OnLogonPrompt is supposed to return S_OK or S_FALSE!");
|
|
|
|
DropConnection();
|
|
goto exit;
|
|
}
|
|
|
|
// If we've reached this point, user hit the "OK" button
|
|
// Check if we're still connected to the IMAP server
|
|
if (irsNOT_CONNECTED < m_irsState) {
|
|
// Still connected! Just try to authenticate
|
|
LoginUser();
|
|
}
|
|
else {
|
|
// Connect to server. We'll authenticate after connection established
|
|
hrResult = Connect(&m_rServer, (boolean) !!m_fConnectAuth, (boolean) !!m_fCommandLogging);
|
|
if (FAILED(hrResult))
|
|
LoadString(g_hLocRes, idsConnectError, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
}
|
|
|
|
exit:
|
|
if (FAILED(hrResult)) {
|
|
// Terminate login procedure and notify user
|
|
OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
|
|
}
|
|
} // ReLoginUser
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Disconnect
|
|
//
|
|
// Purpose:
|
|
// This function issues a LOGOUT command to the IMAP server and waits for
|
|
// the server to process the LOGOUT command before dropping the connection.
|
|
// This allows any currently executing commands to complete their execution.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Disconnect(void)
|
|
{
|
|
return CIxpBase::Disconnect();
|
|
} // Disconnect
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DropConnection
|
|
//
|
|
// Purpose:
|
|
// This function issues a LOGOUT command to the IMAP server (if we
|
|
// currently have nothing in the send queue), then drops the connection
|
|
// before logout command completes.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::DropConnection(void)
|
|
{
|
|
Assert(m_lRefCount >= 0); // This function is called during destruction
|
|
|
|
// You have to be connected to send a LOGOUT: ignore authorization states
|
|
if (IXP_CONNECTED != m_status)
|
|
goto exit; // Just close the CAsyncConn class
|
|
|
|
// We send a logout command IF WE CAN, just as a courtesy. Our main goal
|
|
// is to drop the connection, NOW.
|
|
|
|
// If no commands in our send queue, send Logout command. Note that this
|
|
// is no guarantee that CASyncConn is idle, but at least there's a chance
|
|
if (NULL == m_piciCmdInSending ||
|
|
(m_fIDLE && icIDLE_COMMAND == m_piciCmdInSending->icCommandID &&
|
|
iltPAUSE == m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment->iltFragmentType)) {
|
|
HRESULT hrLogoutResult;
|
|
const char cszLogoutCmd[] = "ZZZZ LOGOUT\r\n";
|
|
char sz[ARRAYSIZE(cszLogoutCmd) + ARRAYSIZE(c_szDONE)]; // Bigger than I need, but who cares
|
|
int iNumBytesSent, iStrLen;
|
|
|
|
// Construct logout or done+logout string
|
|
if (m_fIDLE)
|
|
{
|
|
StrCpyN(sz, c_szDONE, ARRAYSIZE(sz));
|
|
StrCpyN(sz + ARRAYSIZE(c_szDONE) - 1, cszLogoutCmd, (ARRAYSIZE(sz) - ARRAYSIZE(c_szDONE) + 1));
|
|
iStrLen = ARRAYSIZE(c_szDONE) + ARRAYSIZE(cszLogoutCmd) - 2;
|
|
}
|
|
else
|
|
{
|
|
StrCpyN(sz, cszLogoutCmd, ARRAYSIZE(sz));
|
|
iStrLen = ARRAYSIZE(cszLogoutCmd) - 1;
|
|
}
|
|
Assert(iStrLen == lstrlen(sz));
|
|
|
|
hrLogoutResult = m_pSocket->SendBytes(sz, iStrLen, &iNumBytesSent);
|
|
Assert(SUCCEEDED(hrLogoutResult));
|
|
Assert(iNumBytesSent == iStrLen);
|
|
if (m_pLogFile)
|
|
m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT sent");
|
|
}
|
|
else {
|
|
if (m_pLogFile)
|
|
m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT not sent");
|
|
} // else
|
|
|
|
exit:
|
|
// Drop our connection, with status indication
|
|
return CIxpBase::DropConnection();
|
|
} // DropConnection
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ProcessServerGreeting
|
|
//
|
|
// Purpose:
|
|
// This function is invoked when the receiver state machine is in
|
|
// irsSVR_GREETING and a response line is received from the server. This
|
|
// function takes a server greeting line (issued immediately when a
|
|
// connection is established with the IMAP server) and parses it to
|
|
// determine if: a) We are pre-authorized, and therefore do not need to
|
|
// login, b) We have been refused the connection, or c) We must login.
|
|
//
|
|
// Arguments:
|
|
// char *pszResponseLine [in] - the server greeting issued upon connection.
|
|
// DWORD dwNumBytesReceived [in] - length of pszResponseLine string.
|
|
//***************************************************************************
|
|
void CImap4Agent::ProcessServerGreeting(char *pszResponseLine,
|
|
DWORD dwNumBytesReceived)
|
|
{
|
|
HRESULT hrResult;
|
|
IMAP_RESPONSE_ID irResult;
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
BOOL bUseLastResponse;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszResponseLine);
|
|
|
|
// Initialize variables
|
|
szFailureText[0] = '\0';
|
|
hrResult = E_FAIL;
|
|
bUseLastResponse = FALSE;
|
|
|
|
// Whatever happens next, we no longer expect server greeting - change state
|
|
m_irsState = irsIDLE;
|
|
|
|
// We have some kind of server response, so leave the busy state
|
|
AssertSz(m_fBusy, "Check your logic: we should be busy until we get svr greeting!");
|
|
LeaveBusy();
|
|
|
|
// Server response is either OK, BYE or PREAUTH - find out which
|
|
CheckForCompleteResponse(pszResponseLine, dwNumBytesReceived, &irResult);
|
|
|
|
// Even if above fn fails, irResult should be valid (eg, irNONE)
|
|
switch (irResult) {
|
|
case irPREAUTH_RESPONSE:
|
|
// We were pre-authorized by the server! Login is complete.
|
|
// Send capability command
|
|
Assert(ssAuthenticated == m_ssServerState);
|
|
hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND,
|
|
ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
|
|
break;
|
|
|
|
case irBYE_RESPONSE:
|
|
// Server blew us off (ie, issued BYE)! Login failed.
|
|
Assert(ssNotConnected == m_ssServerState);
|
|
hrResult = IXP_E_IMAP_CONNECTION_REFUSED;
|
|
LoadString(g_hLocRes, idsSvrRefusesConnection, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
bUseLastResponse = TRUE;
|
|
break;
|
|
|
|
case irOK_RESPONSE: {
|
|
// Server response was "OK". We need to log in.
|
|
Assert(ssConnecting == m_ssServerState);
|
|
m_ssServerState = ssNonAuthenticated;
|
|
|
|
// Send capability command - on its completion, we'll authenticate
|
|
hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND,
|
|
ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
|
|
break;
|
|
} // case hrIMAP_S_OK_RESPONSE
|
|
|
|
default:
|
|
// Has server gone absolutely LOOPY?
|
|
AssertSz(FALSE, "What kind of server greeting is this?");
|
|
hrResult = E_FAIL;
|
|
LoadString(g_hLocRes, idsUnknownIMAPGreeting, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
bUseLastResponse = TRUE;
|
|
break;
|
|
} // switch(hrResult)
|
|
|
|
if (FAILED(hrResult)) {
|
|
if ('\0' == szFailureText[0]) {
|
|
LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
}
|
|
|
|
// Terminate login procedure and notify user
|
|
OnIMAPError(hrResult, szFailureText, bUseLastResponse);
|
|
DropConnection();
|
|
}
|
|
} // ProcessServerGreeting
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: LoginUser
|
|
//
|
|
// Purpose:
|
|
// This function is responsible for kickstarting the login process.
|
|
//
|
|
// Returns:
|
|
// Nothing, because any errors are reported via CmdCompletionNotification
|
|
// callback to the user. Any errors encountered in this function will be
|
|
// encountered during command transmittal, and so there's nothing further we
|
|
// can do... may as well end the login process here.
|
|
//***************************************************************************
|
|
void CImap4Agent::LoginUser(void)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(ssNotConnected != m_ssServerState);
|
|
AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
|
|
|
|
// Put us in Authentication mode
|
|
OnStatus(IXP_AUTHORIZING);
|
|
|
|
// Check first if we're already authenticated (eg, by PREAUTH greeting)
|
|
if (ssAuthenticated <= m_ssServerState) {
|
|
// We were preauthed. Notify user that login is complete
|
|
OnStatus(IXP_AUTHORIZED);
|
|
return;
|
|
}
|
|
|
|
// Use the old "Login" trick (cleartext passwords and all)
|
|
hrResult = TwoArgCommand("LOGIN", m_rServer.szUserName, m_rServer.szPassword,
|
|
icLOGIN_COMMAND, ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
|
|
|
|
if (FAILED(hrResult)) {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
// Could not send cmd: terminate login procedure and notify user
|
|
LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
|
|
DropConnection();
|
|
}
|
|
} // LoginUser
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AuthenticateUser
|
|
//
|
|
// Purpose:
|
|
// This function handles our behaviour during non-cleartext (SSPI)
|
|
// authentication. It is very heavily based on CPOP3Transport::ResponseAUTH.
|
|
// Note that due to server interpretation problems during testing, I decided
|
|
// that BAD and NO responses will be treated as the same thing for purposes
|
|
// of authentication.
|
|
//
|
|
// Arguments:
|
|
// AUTH_EVENT aeEvent [in] - the authentication event currently occuring.
|
|
// This can be something like aeCONTINUE, for instance.
|
|
// LPSTR pszServerData [in] - any data from the server associated with the
|
|
// current authentication event. Set to NULL if no data is applicable.
|
|
// DWORD dwSizeOfData [in] - the size of the buffer pointed to by
|
|
// pszServerData.
|
|
//***************************************************************************
|
|
void CImap4Agent::AuthenticateUser(AUTH_EVENT aeEvent, LPSTR pszServerData,
|
|
DWORD dwSizeOfData)
|
|
{
|
|
HRESULT hrResult;
|
|
UINT uiFailureTextID;
|
|
BOOL fUseLastResponse;
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
uiFailureTextID = 0;
|
|
fUseLastResponse = FALSE;
|
|
|
|
// Suspend the watchdog for this entire function
|
|
LeaveBusy();
|
|
|
|
// Handle the events for which the current state is unimportant
|
|
if (aeBAD_OR_NO_RESPONSE == aeEvent && asUNINITIALIZED < m_asAuthStatus.asCurrentState) {
|
|
BOOL fTryNextAuthPkg;
|
|
|
|
// Figure out whether we should try the next auth pkg, or re-try current
|
|
if (asWAITFOR_CHALLENGE == m_asAuthStatus.asCurrentState ||
|
|
asWAITFOR_AUTHENTICATION == m_asAuthStatus.asCurrentState)
|
|
fTryNextAuthPkg = tamCURRENT_AUTH_METHOD;
|
|
else
|
|
fTryNextAuthPkg = tamNEXT_AUTH_METHOD;
|
|
|
|
// Send the AUTHENTICATE command
|
|
hrResult = TryAuthMethod(fTryNextAuthPkg, &uiFailureTextID);
|
|
if (FAILED(hrResult))
|
|
// No more auth methods to try: disconnect and end session
|
|
fUseLastResponse = TRUE;
|
|
else {
|
|
// OK, wait for server response
|
|
m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE;
|
|
if (tamCURRENT_AUTH_METHOD == fTryNextAuthPkg)
|
|
m_asAuthStatus.fPromptForCredentials = TRUE;
|
|
}
|
|
|
|
goto exit;
|
|
}
|
|
else if (aeABORT_AUTHENTICATION == aeEvent) {
|
|
// We received an unknown tagged response from the server: bail
|
|
hrResult = E_FAIL;
|
|
uiFailureTextID = idsIMAPAbortAuth;
|
|
fUseLastResponse = TRUE;
|
|
goto exit;
|
|
}
|
|
|
|
|
|
// Now, process auth events based on our current state
|
|
switch (m_asAuthStatus.asCurrentState) {
|
|
case asUNINITIALIZED: {
|
|
BOOL fResult;
|
|
|
|
// Check conditions
|
|
if (aeStartAuthentication != aeEvent) {
|
|
AssertSz(FALSE, "You can only start authentication in this state");
|
|
break;
|
|
}
|
|
Assert(NULL == pszServerData && 0 == dwSizeOfData);
|
|
|
|
// Put us in Authentication mode
|
|
OnStatus(IXP_AUTHORIZING);
|
|
|
|
// Check first if we're already authenticated (eg, by PREAUTH greeting)
|
|
if (ssAuthenticated <= m_ssServerState) {
|
|
// We were preauthed. Notify user that login is complete
|
|
OnStatus(IXP_AUTHORIZED);
|
|
break;
|
|
}
|
|
|
|
// Initialize SSPI
|
|
fResult = FIsSicilyInstalled();
|
|
if (FALSE == fResult) {
|
|
hrResult = E_FAIL;
|
|
uiFailureTextID = idsIMAPSicilyInitFail;
|
|
break;
|
|
}
|
|
|
|
hrResult = SSPIGetPackages(&m_asAuthStatus.pPackages,
|
|
&m_asAuthStatus.cPackages);
|
|
if (FAILED(hrResult)) {
|
|
uiFailureTextID = idsIMAPSicilyPkgFailure;
|
|
break;
|
|
}
|
|
|
|
// Send AUTHENTICATE command
|
|
Assert(0 == m_asAuthStatus.iCurrentAuthToken);
|
|
hrResult = TryAuthMethod(tamNEXT_AUTH_METHOD, &uiFailureTextID);
|
|
if (FAILED(hrResult))
|
|
break;
|
|
|
|
m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE;
|
|
} // case asUNINITIALIZED
|
|
break; // case asUNINITIALIZED
|
|
|
|
|
|
case asWAITFOR_CONTINUE: {
|
|
SSPIBUFFER Negotiate;
|
|
|
|
if (aeCONTINUE != aeEvent) {
|
|
AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
|
|
break;
|
|
}
|
|
|
|
// Server wants us to continue: send negotiation string
|
|
hrResult = SSPILogon(&m_asAuthStatus.rSicInfo, m_asAuthStatus.fPromptForCredentials, SSPI_BASE64,
|
|
m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1], &m_rServer, m_pCBHandler);
|
|
if (FAILED(hrResult)) {
|
|
// Suppress error reportage - user may have hit cancel
|
|
hrResult = CancelAuthentication();
|
|
break;
|
|
}
|
|
|
|
if (m_asAuthStatus.fPromptForCredentials) {
|
|
m_asAuthStatus.fPromptForCredentials = FALSE; // Don't prompt again
|
|
}
|
|
|
|
hrResult = SSPIGetNegotiate(&m_asAuthStatus.rSicInfo, &Negotiate);
|
|
if (FAILED(hrResult)) {
|
|
// Suppress error reportage - user may have hit cancel
|
|
// Or the command was killed (with the connection)
|
|
// Only cancel if we still have a pending command...
|
|
if(m_piciCmdInSending)
|
|
hrResult = CancelAuthentication();
|
|
break;
|
|
}
|
|
|
|
// Append CRLF to negotiation string
|
|
Negotiate.szBuffer[Negotiate.cbBuffer - 1] = '\r';
|
|
Negotiate.szBuffer[Negotiate.cbBuffer] = '\n';
|
|
Negotiate.szBuffer[Negotiate.cbBuffer + 1] = '\0';
|
|
Negotiate.cbBuffer += 2;
|
|
Assert(Negotiate.cbBuffer <= sizeof(Negotiate.szBuffer));
|
|
Assert(Negotiate.szBuffer[Negotiate.cbBuffer - 1] == '\0');
|
|
|
|
hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE,
|
|
Negotiate.szBuffer, Negotiate.cbBuffer - 1);
|
|
if (FAILED(hrResult))
|
|
break;
|
|
|
|
m_asAuthStatus.asCurrentState = asWAITFOR_CHALLENGE;
|
|
} // case asWAITFOR_CONTINUE
|
|
break; // case asWAITFOR_CONTINUE
|
|
|
|
|
|
case asWAITFOR_CHALLENGE: {
|
|
SSPIBUFFER rChallenge, rResponse;
|
|
int iChallengeLen;
|
|
|
|
if (aeCONTINUE != aeEvent) {
|
|
AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
|
|
break;
|
|
}
|
|
|
|
// Server has given us a challenge: respond to challenge
|
|
SSPISetBuffer(pszServerData, SSPI_STRING, 0, &rChallenge);
|
|
|
|
hrResult = SSPIResponseFromChallenge(&m_asAuthStatus.rSicInfo, &rChallenge, &rResponse);
|
|
if (FAILED(hrResult)) {
|
|
// Suppress error reportage - user could have hit cancel
|
|
hrResult = CancelAuthentication();
|
|
break;
|
|
}
|
|
|
|
// Append CRLF to response string
|
|
rResponse.szBuffer[rResponse.cbBuffer - 1] = '\r';
|
|
rResponse.szBuffer[rResponse.cbBuffer] = '\n';
|
|
rResponse.szBuffer[rResponse.cbBuffer + 1] = '\0';
|
|
rResponse.cbBuffer += 2;
|
|
Assert(rResponse.cbBuffer <= sizeof(rResponse.szBuffer));
|
|
Assert(rResponse.szBuffer[rResponse.cbBuffer - 1] == '\0');
|
|
|
|
hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE,
|
|
rResponse.szBuffer, rResponse.cbBuffer - 1);
|
|
if (FAILED(hrResult))
|
|
break;
|
|
|
|
if (FALSE == rResponse.fContinue)
|
|
m_asAuthStatus.asCurrentState = asWAITFOR_AUTHENTICATION;
|
|
} // case asWAITFOR_CHALLENGE
|
|
break; // case asWAITFOR_CHALLENGE
|
|
|
|
|
|
case asWAITFOR_AUTHENTICATION:
|
|
|
|
// If OK response, do nothing
|
|
if (aeOK_RESPONSE != aeEvent) {
|
|
AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
|
|
break;
|
|
}
|
|
break; // case asWAITFOR_AUTHENTICATION
|
|
|
|
|
|
case asCANCEL_AUTHENTICATION:
|
|
AssertSz(aeBAD_OR_NO_RESPONSE == aeEvent, "I cancelled an authentication and didn't get BAD");
|
|
break; // case asCANCEL_AUTHENTICATION
|
|
|
|
|
|
default:
|
|
AssertSz(FALSE, "Invalid or unhandled state?");
|
|
break; // Default case
|
|
} // switch (aeEvent)
|
|
|
|
exit:
|
|
if (FAILED(hrResult)) {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
char szFailureFmt[MAX_RESOURCESTRING/4];
|
|
char szGeneral[MAX_RESOURCESTRING/4]; // Ack, how big could the word, "General" be?
|
|
LPSTR p, pszAuthPkg;
|
|
|
|
LoadString(g_hLocRes, idsIMAPAuthFailedFmt, szFailureFmt, ARRAYSIZE(szFailureFmt));
|
|
if (0 == m_asAuthStatus.iCurrentAuthToken) {
|
|
LoadString(g_hLocRes, idsGeneral, szGeneral, ARRAYSIZE(szGeneral));
|
|
pszAuthPkg = szGeneral;
|
|
}
|
|
else
|
|
pszAuthPkg = m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1];
|
|
|
|
p = szFailureText;
|
|
p += wnsprintf(szFailureText, ARRAYSIZE(szFailureText), szFailureFmt, pszAuthPkg);
|
|
if (0 != uiFailureTextID)
|
|
LoadString(g_hLocRes, uiFailureTextID, p,
|
|
ARRAYSIZE(szFailureText) - (DWORD) (p - szFailureText));
|
|
OnIMAPError(hrResult, szFailureText, fUseLastResponse);
|
|
|
|
DropConnection();
|
|
}
|
|
// Reawaken the watchdog, if required
|
|
else if (FALSE == m_fBusy &&
|
|
(NULL != m_piciPendingList || NULL != m_piciCmdInSending)) {
|
|
hrResult = HrEnterBusy();
|
|
Assert(SUCCEEDED(hrResult));
|
|
}
|
|
} // AuthenticateUser
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: TryAuthMethod
|
|
//
|
|
// Purpose:
|
|
// This function sends out an AUTHENTICATE command to the server with the
|
|
// appropriate authentication method. The caller can choose which method is
|
|
// more appropriate: he can re-try the current authentication method, or
|
|
// move on to the next authentication command which is supported by both
|
|
// server and client.
|
|
//
|
|
// Arguments:
|
|
// BOOL fNextAuthMethod [in] - TRUE if we should attempt to move on to
|
|
// the next authentication package. FALSE if we should re-try the
|
|
// current authentication package.
|
|
// UINT *puiFailureTextID [out] - in case of failure, (eg, no more auth
|
|
// methods to try), this function returns a string resource ID here which
|
|
// describes the error.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Expected failure codes include:
|
|
// IXP_E_IMAP_AUTH_NOT_POSSIBLE - indicates server does not support
|
|
// any auth packages which are recognized on this computer.
|
|
// IXP_E_IMAP_OUT_OF_AUTH_METHODS - indicates that one or more auth
|
|
// methods were attempted, and no more auth methods are left to try.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::TryAuthMethod(BOOL fNextAuthMethod, UINT *puiFailureTextID)
|
|
{
|
|
BOOL fFoundMatch;
|
|
HRESULT hrResult;
|
|
char szBuffer[CMDLINE_BUFSIZE];
|
|
CIMAPCmdInfo *piciCommand;
|
|
int iStartingAuthToken;
|
|
LPSTR p;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
piciCommand = NULL;
|
|
|
|
// Only accept cmds if server is in proper state
|
|
if (ssNonAuthenticated != m_ssServerState) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
// If we've already tried an auth pkg, free its info
|
|
if (0 != m_asAuthStatus.iCurrentAuthToken)
|
|
SSPIFreeContext(&m_asAuthStatus.rSicInfo);
|
|
|
|
// Find the next auth token (returned by svr) that we support on this computer
|
|
fFoundMatch = FALSE;
|
|
iStartingAuthToken = m_asAuthStatus.iCurrentAuthToken;
|
|
while (fFoundMatch == FALSE &&
|
|
m_asAuthStatus.iCurrentAuthToken < m_asAuthStatus.iNumAuthTokens &&
|
|
fNextAuthMethod) {
|
|
ULONG ul = 0;
|
|
|
|
// Current m_asAuthStatus.iCurrentAuthToken serves as idx to NEXT auth token
|
|
// Compare current auth token with all installed packages
|
|
for (ul = 0; ul < m_asAuthStatus.cPackages; ul++) {
|
|
if (0 == lstrcmpi(m_asAuthStatus.pPackages[ul].pszName,
|
|
m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken])) {
|
|
fFoundMatch = TRUE;
|
|
break;
|
|
} // if
|
|
} // for
|
|
|
|
// Update this to indicate the current auth token ORDINAL (not idx)
|
|
m_asAuthStatus.iCurrentAuthToken += 1;
|
|
} // while
|
|
|
|
if (FALSE == fFoundMatch && fNextAuthMethod) {
|
|
// Could not find next authentication method match-up
|
|
if (0 == iStartingAuthToken) {
|
|
*puiFailureTextID = idsIMAPAuthNotPossible;
|
|
return IXP_E_IMAP_AUTH_NOT_POSSIBLE;
|
|
}
|
|
else {
|
|
*puiFailureTextID = idsIMAPOutOfAuthMethods;
|
|
return IXP_E_IMAP_OUT_OF_AUTH_METHODS;
|
|
}
|
|
}
|
|
|
|
// OK, m_asAuthStatus.iCurrentAuthToken should now point to correct match
|
|
piciCommand = new CIMAPCmdInfo(this, icAUTHENTICATE_COMMAND, ssNonAuthenticated,
|
|
0, 0, NULL);
|
|
if (NULL == piciCommand) {
|
|
*puiFailureTextID = idsMemory;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Construct command line
|
|
p = szBuffer;
|
|
p += wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s %.300s\r\n", piciCommand->szTag, "AUTHENTICATE",
|
|
m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1]);
|
|
|
|
// Send command
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, (DWORD) (p - szBuffer));
|
|
if (FAILED(hrResult))
|
|
goto SendError;
|
|
|
|
// Insert a pause, so we can perform challenge/response
|
|
hrResult = SendPause(piciCommand);
|
|
if (FAILED(hrResult))
|
|
goto SendError;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
SendError:
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
return hrResult;
|
|
} // TryAuthMethod
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: CancelAuthentication
|
|
//
|
|
// Purpose:
|
|
// This function cancels the authentication currently in progress,
|
|
// typically due to a failure result from a Sicily function. It sends a "*"
|
|
// to the server and puts us into cancellation mode.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::CancelAuthentication(void)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE, "*\r\n", 3);
|
|
m_asAuthStatus.asCurrentState = asCANCEL_AUTHENTICATION;
|
|
return hrResult;
|
|
} // CancelAuthentication
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnCommandCompletion
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we have received a tagged response line
|
|
// terminating the current command in progress, whether or not the command
|
|
// result was successful or not. This function notifies the user of the
|
|
// command's results, and handles other tasks such as updating our internal
|
|
// mirror of the server state and calling notification functions.
|
|
//
|
|
// Arguments:
|
|
// LPSTR szTag [in] - the tag found in the tagged response line. This will
|
|
// be used to compare with a list of commands in progress when we allow
|
|
// multiple simultaneous commands, but is not currently used.
|
|
// HRESULT hrCompletionResult [in] - the HRESULT returned by the IMAP line
|
|
// parsing functions, eg, S_OK or IXP_E_IMAP_SVR_SYNTAXERR.
|
|
// IMAP_RESPONSE_ID irCompletionResponse [in] - identifies the status response
|
|
// of the tagged response line (OK/NO/BAD).
|
|
//***************************************************************************
|
|
void CImap4Agent::OnCommandCompletion(LPSTR szTag, HRESULT hrCompletionResult,
|
|
IMAP_RESPONSE_ID irCompletionResponse)
|
|
{
|
|
CIMAPCmdInfo *piciCompletedCmd;
|
|
boolean bSuppressCompletionNotification;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != szTag);
|
|
Assert(NULL != m_piciPendingList || NULL != m_piciCmdInSending);
|
|
|
|
bSuppressCompletionNotification = FALSE;
|
|
|
|
// ** STEP ONE: Identify the corresponding command for given tagged response
|
|
// Search the pending-command chain for the given tag
|
|
piciCompletedCmd = RemovePendingCommand(szTag);
|
|
if (NULL == piciCompletedCmd) {
|
|
BOOL fLeaveBusy = FALSE;
|
|
|
|
// Couldn't find in pending list, check the command in sending
|
|
EnterCriticalSection(&m_csSendQueue);
|
|
if (NULL != m_piciCmdInSending &&
|
|
0 == lstrcmp(szTag, m_piciCmdInSending->szTag)) {
|
|
piciCompletedCmd = DequeueCommand();
|
|
fLeaveBusy = TRUE;
|
|
}
|
|
else {
|
|
AssertSz(FALSE, "Could not find cmd corresponding to tagged response!");
|
|
}
|
|
LeaveCriticalSection(&m_csSendQueue);
|
|
|
|
// Now we're out of &m_csSendQueue, call LeaveBusy (needs m_cs). Avoids deadlock.
|
|
if (fLeaveBusy)
|
|
LeaveBusy(); // This needs CIxpBase::m_cs, so having &m_csSendQueue may deadlock
|
|
}
|
|
|
|
// Did we find a command which matches the given tag?
|
|
if (NULL == piciCompletedCmd)
|
|
return; // $REVIEW: Should probably return an error to user
|
|
// $REVIEW: I don't think I need to bother to pump the send queue
|
|
|
|
|
|
|
|
// ** STEP TWO: Perform end-of-command actions
|
|
// Translate hrCompletionResult depending on response received
|
|
switch (irCompletionResponse) {
|
|
case irOK_RESPONSE:
|
|
Assert(S_OK == hrCompletionResult);
|
|
break;
|
|
|
|
case irNO_RESPONSE:
|
|
Assert(S_OK == hrCompletionResult);
|
|
hrCompletionResult = IXP_E_IMAP_TAGGED_NO_RESPONSE;
|
|
break;
|
|
|
|
case irBAD_RESPONSE:
|
|
Assert(S_OK == hrCompletionResult);
|
|
hrCompletionResult = IXP_E_IMAP_BAD_RESPONSE;
|
|
break;
|
|
|
|
default:
|
|
// If none of the above, hrResult had better be failure
|
|
Assert(FAILED(hrCompletionResult));
|
|
break;
|
|
}
|
|
|
|
// Perform any actions which follow the successful (or unsuccessful)
|
|
// completion of an IMAP command
|
|
switch (piciCompletedCmd->icCommandID) {
|
|
case icAUTHENTICATE_COMMAND: {
|
|
AUTH_EVENT aeEvent;
|
|
|
|
// We always suppress completion notification for this command,
|
|
// because it is sent by internal code (not by the user)
|
|
bSuppressCompletionNotification = TRUE;
|
|
|
|
if (irOK_RESPONSE == irCompletionResponse)
|
|
aeEvent = aeOK_RESPONSE;
|
|
else if (irNO_RESPONSE == irCompletionResponse ||
|
|
irBAD_RESPONSE == irCompletionResponse)
|
|
aeEvent = aeBAD_OR_NO_RESPONSE;
|
|
else
|
|
aeEvent = aeABORT_AUTHENTICATION;
|
|
|
|
AuthenticateUser(aeEvent, NULL, 0);
|
|
|
|
if (SUCCEEDED(hrCompletionResult)) {
|
|
m_ssServerState = ssAuthenticated;
|
|
AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
|
|
OnStatus(IXP_AUTHORIZED);
|
|
}
|
|
|
|
// Make sure we were paused
|
|
Assert(iltPAUSE == piciCompletedCmd->pilqCmdLineQueue->
|
|
pilfFirstFragment->iltFragmentType);
|
|
} // case icAUTHENTICATE_COMMAND
|
|
break; // case icAUTHENTICATE_COMMAND
|
|
|
|
case icLOGIN_COMMAND:
|
|
// We always suppress completion notification for this command,
|
|
// because it is sent by internal code (not by the user)
|
|
bSuppressCompletionNotification = TRUE;
|
|
|
|
if (SUCCEEDED(hrCompletionResult)) {
|
|
m_ssServerState = ssAuthenticated;
|
|
AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
|
|
OnStatus(IXP_AUTHORIZED);
|
|
}
|
|
else {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
Assert(ssAuthenticated > m_ssServerState);
|
|
LoadString(g_hLocRes, idsFailedLogin, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
OnIMAPError(IXP_E_IMAP_LOGINFAILURE, szFailureText, USE_LAST_RESPONSE);
|
|
ReLoginUser(); // Re-attempt login
|
|
} // else
|
|
|
|
break; // case icLOGIN_COMMAND
|
|
|
|
case icCAPABILITY_COMMAND:
|
|
// We always suppress completion notification for this command
|
|
// because it is sent by internal code (not by the user)
|
|
bSuppressCompletionNotification = TRUE;
|
|
|
|
if (SUCCEEDED(hrCompletionResult)) {
|
|
AssertSz(m_fConnectAuth, "Now just HOW does IIMAPTransport user do auth?");
|
|
if (m_rServer.fTrySicily)
|
|
AuthenticateUser(aeStartAuthentication, NULL, 0);
|
|
else
|
|
LoginUser();
|
|
}
|
|
else {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
// Stop login process and report error to caller
|
|
LoadString(g_hLocRes, idsIMAPFailedCapability, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
OnIMAPError(hrCompletionResult, szFailureText, USE_LAST_RESPONSE);
|
|
DropConnection();
|
|
}
|
|
|
|
break; // case icCAPABILITY_COMMAND
|
|
|
|
|
|
case icSELECT_COMMAND:
|
|
case icEXAMINE_COMMAND:
|
|
if (SUCCEEDED(hrCompletionResult))
|
|
m_ssServerState = ssSelected;
|
|
else
|
|
m_ssServerState = ssAuthenticated;
|
|
|
|
break; // case icSELECT_COMMAND and icEXAMINE_COMMAND
|
|
|
|
case icCLOSE_COMMAND:
|
|
// $REVIEW: Should tagged NO response also go to ssAuthenticated?
|
|
if (SUCCEEDED(hrCompletionResult)) {
|
|
m_ssServerState = ssAuthenticated;
|
|
ResetMsgSeqNumToUID();
|
|
}
|
|
|
|
break; // case icCLOSE_COMMAND
|
|
|
|
case icLOGOUT_COMMAND:
|
|
// We always suppress completion notification for this command
|
|
bSuppressCompletionNotification = TRUE; // User can't send logout: it's sent internally
|
|
|
|
// Drop the connection (without status indication) regardless of
|
|
// whether LOGOUT succeeded or failed
|
|
Assert(SUCCEEDED(hrCompletionResult)); // Debug-only detection of hanky-panky
|
|
m_pSocket->Close();
|
|
ResetMsgSeqNumToUID(); // Just in case, SHOULD be handled by OnDisconnected,FreeAllData
|
|
|
|
break; // case icLOGOUT_COMMAND;
|
|
|
|
case icIDLE_COMMAND:
|
|
bSuppressCompletionNotification = TRUE; // User can't send IDLE: it's sent internally
|
|
m_fIDLE = FALSE; // We are now out of IDLE mode
|
|
break; // case icIDLE_COMMAND
|
|
|
|
case icAPPEND_COMMAND:
|
|
m_dwAppendStreamUploaded = 0;
|
|
m_dwAppendStreamTotal = 0;
|
|
break; // case icAPPEND_COMMAND
|
|
} // switch (piciCompletedCmd->icCommandID)
|
|
|
|
|
|
// ** STEP THREE: Perform notifications.
|
|
// Notify the user that this command has completed, unless we're told to
|
|
// suppress it (usually done to treat the multi-step login process as
|
|
// one operation).
|
|
if (FALSE == bSuppressCompletionNotification) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
irIMAPResponse.wParam = piciCompletedCmd->wParam;
|
|
irIMAPResponse.lParam = piciCompletedCmd->lParam;
|
|
irIMAPResponse.hrResult = hrCompletionResult;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
|
|
OnIMAPResponse(piciCompletedCmd->pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
// Delete CIMAPCmdInfo object
|
|
// Note that deleting a CIMAPCmdInfo object automatically flushes its send queue
|
|
delete piciCompletedCmd;
|
|
|
|
// Finally, pump the send queue, if another cmd is available
|
|
if (NULL != m_piciSendQueue)
|
|
ProcessSendQueue(iseSEND_COMMAND);
|
|
else if (NULL == m_piciPendingList &&
|
|
m_ssServerState >= ssAuthenticated && irsIDLE == m_irsState)
|
|
// Both m_piciSendQueue and m_piciPendingList are empty: send IDLE cmd
|
|
EnterIdleMode();
|
|
} // OnCommandCompletion
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: CheckForCompleteResponse
|
|
//
|
|
// Purpose:
|
|
// Given a response line (which isn't part of a literal), this function
|
|
// checks the end of the line to see if a literal is coming. If so, then we
|
|
// prepare the receiver FSM for it. Otherwise, this constitutes the end
|
|
// of an IMAP response, so we may parse as required.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszResponseLine [in] - this points to the response line sent to
|
|
// us by the IMAP server.
|
|
// DWORD dwNumBytesRead [in] - the length of pszResponseLine.
|
|
// IMAP_RESPONSE_ID *pirParseResult [out] - if the function determines that
|
|
// we can parse the response, the parse result is stored here (eg,
|
|
// irOK_RESPONSE). Otherwise, irNONE is written to the pointed location.
|
|
//***************************************************************************
|
|
void CImap4Agent::CheckForCompleteResponse(LPSTR pszResponseLine,
|
|
DWORD dwNumBytesRead,
|
|
IMAP_RESPONSE_ID *pirParseResult)
|
|
{
|
|
HRESULT hrResult;
|
|
boolean bTagged;
|
|
IMAP_LINE_FRAGMENT *pilfLine;
|
|
LPSTR psz;
|
|
BOOL fLiteral = FALSE;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszResponseLine);
|
|
Assert(NULL == m_pilfLiteralInProgress);
|
|
Assert(0 == m_dwLiteralInProgressBytesLeft);
|
|
Assert(NULL != pirParseResult);
|
|
Assert(irsIDLE == m_irsState || irsSVR_GREETING == m_irsState);
|
|
|
|
*pirParseResult = irNONE;
|
|
|
|
// This is a LINE (not literal), so we're OK to nuke CRLF at end
|
|
Assert(dwNumBytesRead >= 2); // All lines must have at least CRLF
|
|
*(pszResponseLine + dwNumBytesRead - 2) = '\0';
|
|
|
|
// Create line fragment
|
|
pilfLine = new IMAP_LINE_FRAGMENT;
|
|
pilfLine->iltFragmentType = iltLINE;
|
|
pilfLine->ilsLiteralStoreType = ilsSTRING;
|
|
pilfLine->dwLengthOfFragment = dwNumBytesRead - 2; // Subtract nuked CRLF
|
|
pilfLine->data.pszSource = pszResponseLine;
|
|
pilfLine->pilfNextFragment = NULL;
|
|
pilfLine->pilfPrevFragment = NULL;
|
|
|
|
EnqueueFragment(pilfLine, &m_ilqRecvQueue);
|
|
|
|
// Now check last char in line (exclude CRLF) to see if a literal is forthcoming
|
|
psz = pszResponseLine + dwNumBytesRead -
|
|
min(dwNumBytesRead, 3); // Points to '}' if literal is coming
|
|
if ('}' == *psz) {
|
|
LPSTR pszLiteral;
|
|
|
|
// IE5 bug #30672: It is valid for a line to end in "}" and not be a literal.
|
|
// We must confirm that there are digits and an opening brace "{" to detect a literal
|
|
pszLiteral = psz;
|
|
while (TRUE) {
|
|
pszLiteral -= 1;
|
|
|
|
if (pszLiteral < pszResponseLine)
|
|
break;
|
|
|
|
if ('{' == *pszLiteral) {
|
|
fLiteral = TRUE;
|
|
psz = pszLiteral;
|
|
break;
|
|
}
|
|
else if (*pszLiteral < '0' || *pszLiteral > '9')
|
|
// Assert(FALSE) (placeholder)
|
|
// *** Consider using isdigit or IsDigit? ***
|
|
break; // This is not a literal
|
|
}
|
|
}
|
|
|
|
if (FALSE == fLiteral) {
|
|
char szTag[NUM_TAG_CHARS+1];
|
|
// No literal is forthcoming. This is a complete line, so let's parse
|
|
|
|
// Get ptr to first fragment, then nuke receive queue so we can
|
|
// continue to receive response lines while parsing this one
|
|
pilfLine = m_ilqRecvQueue.pilfFirstFragment;
|
|
m_ilqRecvQueue = ImapLinefragQueue_INIT;
|
|
|
|
// Parse line. Note that parsing code is responsible for advancing
|
|
// pilfLine so that it points to the current fragment being parsed.
|
|
// Fragments which have been fully processed should be freed by
|
|
// the parsing code (except for the last fragment)
|
|
hrResult = ParseSvrResponseLine(&pilfLine, &bTagged, szTag, pirParseResult);
|
|
|
|
// Flush rest of recv queue, regardless of parse result
|
|
while (NULL != pilfLine) {
|
|
IMAP_LINE_FRAGMENT *pilfTemp;
|
|
|
|
pilfTemp = pilfLine->pilfNextFragment;
|
|
FreeFragment(&pilfLine);
|
|
pilfLine = pilfTemp;
|
|
}
|
|
|
|
if (bTagged)
|
|
OnCommandCompletion(szTag, hrResult, *pirParseResult);
|
|
else if (FAILED(hrResult)) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Report untagged response failures via ErrorNotification callback
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, *pirParseResult);
|
|
irIMAPResponse.hrResult = hrResult;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtERROR_NOTIFICATION;
|
|
|
|
// Log it
|
|
if (m_pLogFile) {
|
|
char szErrorTxt[64];
|
|
|
|
wnsprintf(szErrorTxt, ARRAYSIZE(szErrorTxt), "PARSE ERROR: hr=%lu", hrResult);
|
|
m_pLogFile->WriteLog(LOGFILE_DB, szErrorTxt);
|
|
}
|
|
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
}
|
|
}
|
|
else {
|
|
DWORD dwLengthOfLiteral, dwMsgSeqNum;
|
|
LPSTR pszBodyTag;
|
|
|
|
if ('{' != *psz) {
|
|
Assert(FALSE); // What is this?
|
|
return; // Nothing we can do, we obviously can't get size of literal
|
|
}
|
|
else
|
|
dwLengthOfLiteral = StrToUint(psz + 1);
|
|
|
|
// Prepare either for FETCH body, or a regular literal
|
|
if (isFetchResponse(&m_ilqRecvQueue, &dwMsgSeqNum) &&
|
|
isFetchBodyLiteral(pilfLine, psz, &pszBodyTag)) {
|
|
// Prepare (tombstone) literal first, because it puts us in literal mode
|
|
hrResult = PrepareForLiteral(0);
|
|
|
|
// This will override literal mode, putting us in fetch body part mode
|
|
// Ignore PrepareForLiteral failure: if we don't, we interpret EACH LINE of
|
|
// the fetch body as an IMAP response line.
|
|
PrepareForFetchBody(dwMsgSeqNum, dwLengthOfLiteral, pszBodyTag);
|
|
}
|
|
else
|
|
hrResult = PrepareForLiteral(dwLengthOfLiteral);
|
|
|
|
Assert(SUCCEEDED(hrResult)); // Not much else we can do
|
|
} // else: handles case where a literal is coming after this line
|
|
} // CheckForCompleteResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: PrepareForLiteral
|
|
//
|
|
// Purpose:
|
|
// This function prepares the receiver code to receive a literal from the
|
|
// IMAP server.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwSizeOfLiteral [in] - the size of the incoming literal as
|
|
// reported by the IMAP server.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::PrepareForLiteral(DWORD dwSizeOfLiteral)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfLiteral;
|
|
HRESULT hrResult;
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
|
|
// Construct line fragment of type iltLITERAL
|
|
Assert(NULL == m_pilfLiteralInProgress);
|
|
pilfLiteral = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfLiteral)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfLiteral->iltFragmentType = iltLITERAL;
|
|
pilfLiteral->dwLengthOfFragment = dwSizeOfLiteral;
|
|
pilfLiteral->pilfNextFragment = NULL;
|
|
pilfLiteral->pilfPrevFragment = NULL;
|
|
|
|
// Allocate string or stream to hold literal, depending on its size
|
|
if (pilfLiteral->dwLengthOfFragment > dwLITERAL_THRESHOLD) {
|
|
// Literal is big, so store it as stream (large literals often represent
|
|
// data which we return to the user as a stream, eg, message bodies)
|
|
pilfLiteral->ilsLiteralStoreType = ilsSTREAM;
|
|
hrResult = MimeOleCreateVirtualStream(&pilfLiteral->data.pstmSource);
|
|
}
|
|
else {
|
|
BOOL bResult;
|
|
|
|
// Literal is small. Store it as a string rather than a stream, since
|
|
// CImap4Agent functions probably expect it as a string, anyways.
|
|
pilfLiteral->ilsLiteralStoreType = ilsSTRING;
|
|
bResult = MemAlloc((void **) &pilfLiteral->data.pszSource,
|
|
pilfLiteral->dwLengthOfFragment + 1); // Room for null-term
|
|
if (FALSE == bResult)
|
|
hrResult = E_OUTOFMEMORY;
|
|
else {
|
|
hrResult = S_OK;
|
|
*(pilfLiteral->data.pszSource) = '\0'; // Null-terminate the string
|
|
}
|
|
}
|
|
|
|
if (FAILED(hrResult))
|
|
delete pilfLiteral; // Failure means no data.pstmSource or data.pszSource to dealloc
|
|
else {
|
|
// Set up receive FSM to receive the proper number of bytes for literal
|
|
m_pilfLiteralInProgress = pilfLiteral;
|
|
m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral;
|
|
m_irsState = irsLITERAL;
|
|
}
|
|
|
|
return hrResult;
|
|
} // PrepareForLiteral
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isFetchResponse
|
|
//
|
|
// Purpose:
|
|
// This function determines if the given IMAP line fragment queue holds
|
|
// a FETCH response. If so, its message sequence number may be returned to
|
|
// the caller.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINEFRAG_QUEUE *pilqCurrentResponse [in] - a line fragment queue
|
|
// which may or may not hold a FETCH response.
|
|
// LPDWORD pdwMsgSeqNum [out] - if pilqCurrentResponse points to a FETCH
|
|
// response, its message sequence number is returned here. This argument
|
|
// may be NULL if the user does not care.
|
|
//
|
|
// Returns:
|
|
// TRUE if pilqCurrentResponse held a FETCH response. Otherwise, FALSE.
|
|
//***************************************************************************
|
|
BOOL CImap4Agent::isFetchResponse(IMAP_LINEFRAG_QUEUE *pilqCurrentResponse,
|
|
LPDWORD pdwMsgSeqNum)
|
|
{
|
|
LPSTR pszMsgSeqNum;
|
|
|
|
Assert(NULL != pilqCurrentResponse);
|
|
Assert(NULL != pilqCurrentResponse->pilfFirstFragment);
|
|
Assert(iltLINE == pilqCurrentResponse->pilfFirstFragment->iltFragmentType);
|
|
|
|
if (NULL != pdwMsgSeqNum)
|
|
*pdwMsgSeqNum = 0; // At least it won't be random
|
|
|
|
pszMsgSeqNum = pilqCurrentResponse->pilfFirstFragment->data.pszSource;
|
|
// Advance pointer to the message sequence number
|
|
if ('*' != *pszMsgSeqNum)
|
|
return FALSE; // We only handle tagged responses
|
|
|
|
pszMsgSeqNum += 1;
|
|
if (cSPACE != *pszMsgSeqNum)
|
|
return FALSE;
|
|
|
|
pszMsgSeqNum += 1;
|
|
if (*pszMsgSeqNum >= '0' && *pszMsgSeqNum <= '9') {
|
|
LPSTR pszEndOfNumber;
|
|
int iResult;
|
|
|
|
pszEndOfNumber = StrChr(pszMsgSeqNum, cSPACE); // Find the end of the number
|
|
if (NULL == pszEndOfNumber)
|
|
return FALSE; // This ain't no FETCH response
|
|
|
|
iResult = StrCmpNI(pszEndOfNumber + 1, "FETCH ", 6);
|
|
if (0 == iResult) {
|
|
if (NULL != pdwMsgSeqNum)
|
|
*pdwMsgSeqNum = StrToUint(pszMsgSeqNum);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// If we hit this point, it wasn't a FETCH response
|
|
return FALSE;
|
|
} // isFetchResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isFetchBodyLiteral
|
|
//
|
|
// Purpose:
|
|
// This function is called when the caller knows he has a FETCH response,
|
|
// and when the FETCH response is about to send a literal. This function will
|
|
// determine whether the literal about to be sent contains a message body
|
|
// part (like RFC822), or whether the literal is something else (like an
|
|
// nstring sent as a literal inside a BODYSTRUCTURE).
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT *pilfCurrent [in] - a pointer to the current line
|
|
// fragment received from the server. It is used by this function to
|
|
// rewind past any literals we may have received in the "section" of
|
|
// the BODY "msg_att" (see RFC2060 formal syntax).
|
|
// LPSTR pszStartOfLiteralSize [in] - a pointer to the start of the '{'
|
|
// character which indicates that a literal is coming (eg, {123}
|
|
// indicates a literal of size 123 is coming, and pszStartOfLiteralSize
|
|
// would point to the '{' in this case).
|
|
// LPSTR *ppszBodyTag [out] - if the literal about to be sent contains a
|
|
// message body part, a dup of the tag (eg, "RFC822" or "BODY[2.2]") is
|
|
// returned to the caller here. It is the caller's responsibility to
|
|
// MemFree this tag. THIS TAG WILL NOT CONTAIN ANY SPACES. Thus even though
|
|
// the server may return "BODY[HEADER.FIELDS (foo bar)]", this function
|
|
// only returns "BODY[HEADER.FIELDS".
|
|
//
|
|
// Returns:
|
|
// TRUE if the literal about to be sent contains a message body part.
|
|
// FALSE otherwise.
|
|
//***************************************************************************
|
|
BOOL CImap4Agent::isFetchBodyLiteral(IMAP_LINE_FRAGMENT *pilfCurrent,
|
|
LPSTR pszStartOfLiteralSize,
|
|
LPSTR *ppszBodyTag)
|
|
{
|
|
LPSTR pszStartOfLine;
|
|
LPSTR pszStartOfFetchAtt;
|
|
LPSTR pszMostRecentSpace;
|
|
int iNumDelimiters;
|
|
BOOL fBodySection = FALSE;
|
|
|
|
Assert(NULL != pilfCurrent);
|
|
Assert(NULL != pszStartOfLiteralSize);
|
|
Assert(pszStartOfLiteralSize >= pilfCurrent->data.pszSource &&
|
|
pszStartOfLiteralSize < (pilfCurrent->data.pszSource + pilfCurrent->dwLengthOfFragment));
|
|
Assert(NULL != ppszBodyTag);
|
|
|
|
// Initialize variables
|
|
*ppszBodyTag = NULL;
|
|
Assert('{' == *pszStartOfLiteralSize);
|
|
|
|
// Get pointer to current msg_att: we only care about RFC822* or BODY[...]. ENVELOPE ({5} doesn't count
|
|
iNumDelimiters = 0;
|
|
pszStartOfLine = pilfCurrent->data.pszSource;
|
|
pszStartOfFetchAtt = pszStartOfLiteralSize;
|
|
pszMostRecentSpace = pszStartOfLiteralSize;
|
|
while (iNumDelimiters < 2) {
|
|
// Check if we have recoiled to the start of current string buffer
|
|
if (pszStartOfFetchAtt <= pszStartOfLine) {
|
|
// We need to recoil to previous string buffer. It is likely that a literal
|
|
// is in the way, and it is likely that this literal belongs to HEADER.FIELDS
|
|
// (but this can also happen inside an ENVELOPE)
|
|
|
|
// Skip literals and anything else that's not a line
|
|
do {
|
|
pilfCurrent = pilfCurrent->pilfPrevFragment;
|
|
} while (NULL != pilfCurrent && iltLINE != pilfCurrent->iltFragmentType);
|
|
|
|
if (NULL == pilfCurrent || 0 == pilfCurrent->dwLengthOfFragment) {
|
|
// This ain't no FETCH BODY, near as I can tell
|
|
Assert(iNumDelimiters < 2);
|
|
break;
|
|
}
|
|
else {
|
|
// Reset string pointers
|
|
Assert(iltLINE == pilfCurrent->iltFragmentType &&
|
|
ilsSTRING == pilfCurrent->ilsLiteralStoreType);
|
|
pszStartOfLine = pilfCurrent->data.pszSource;
|
|
|
|
// Note that pszStartOfFetchAtt will recoil past literal size decl ("{123}")
|
|
// That's OK because it won't contain any of the delimiters we're looking for
|
|
pszStartOfFetchAtt = pszStartOfLine + pilfCurrent->dwLengthOfFragment; // Points to null-term
|
|
pszMostRecentSpace = pszStartOfFetchAtt; // Points to null-term (that's OK)
|
|
}
|
|
}
|
|
|
|
// Set pszMostRecentSpace before pszStartOfFetchAtt decrement so pszMostRecentSpace
|
|
// isn't set to the space BEFORE the fetch body tag
|
|
if (cSPACE == *pszStartOfFetchAtt)
|
|
pszMostRecentSpace = pszStartOfFetchAtt;
|
|
|
|
pszStartOfFetchAtt -= 1;
|
|
|
|
// Check for nested brackets (should not be allowed)
|
|
Assert(']' != *pszStartOfFetchAtt || fBodySection == FALSE);
|
|
|
|
// Disable delimiter-counting if we're in the middle of RFC2060 formal syntax "section"
|
|
// because the HEADER.FIELDS (...) section contains spaces and parentheses
|
|
if (']' == *pszStartOfFetchAtt)
|
|
fBodySection = TRUE;
|
|
else if ('[' == *pszStartOfFetchAtt)
|
|
fBodySection = FALSE;
|
|
|
|
if (FALSE == fBodySection && (cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt))
|
|
iNumDelimiters += 1;
|
|
}
|
|
|
|
if (iNumDelimiters < 2)
|
|
return FALSE; // This isn't a body tag
|
|
|
|
Assert(2 == iNumDelimiters);
|
|
Assert(cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt);
|
|
pszStartOfFetchAtt += 1; // Make it point to the start of the tag
|
|
if (0 == StrCmpNI(pszStartOfFetchAtt, "RFC822", 6) ||
|
|
0 == StrCmpNI(pszStartOfFetchAtt, "BODY[", 5)) {
|
|
int iSizeOfBodyTag;
|
|
BOOL fResult;
|
|
|
|
Assert(pszMostRecentSpace >= pszStartOfLine && (NULL == pilfCurrent ||
|
|
pszMostRecentSpace <= pszStartOfLine + pilfCurrent->dwLengthOfFragment));
|
|
Assert(pszStartOfFetchAtt >= pszStartOfLine && (NULL == pilfCurrent ||
|
|
pszStartOfFetchAtt <= pszStartOfLine + pilfCurrent->dwLengthOfFragment));
|
|
Assert(pszMostRecentSpace >= pszStartOfFetchAtt);
|
|
|
|
// Return a duplicate of the body tag, up until the first space +1 for null-term
|
|
iSizeOfBodyTag = (int) (pszMostRecentSpace - pszStartOfFetchAtt + 1);
|
|
fResult = MemAlloc((void **)ppszBodyTag, iSizeOfBodyTag);
|
|
if (FALSE == fResult)
|
|
return FALSE;
|
|
|
|
CopyMemory(*ppszBodyTag, pszStartOfFetchAtt, iSizeOfBodyTag);
|
|
*(*ppszBodyTag + iSizeOfBodyTag - 1) = '\0'; // Null-terminate the body tag dup
|
|
return TRUE;
|
|
}
|
|
|
|
// If we reached this point, this is not a body tag
|
|
return FALSE;
|
|
} // isFetchBodyLiteral
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: PrepareForFetchBody
|
|
//
|
|
// Purpose:
|
|
// This function prepares the receiver code to receive a literal which
|
|
// contains a message body part. This literal will always be part of a FETCH
|
|
// response from the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwMsgSeqNum [in] - the message sequence number of the FETCH
|
|
// response currently being received from the IMAP server.
|
|
// DWORD dwSizeOfLiteral [in] - the size of the literal about to be received
|
|
// from the server.
|
|
// LPSTR pszBodyTag [in] - a pointer to a dup of the IMAP msg_att (eg,
|
|
// "RFC822" or "BODY[2.2]") which identifies the current literal. Look up
|
|
// msg_att in RFC2060's formal syntax section for details. This dup will
|
|
// be MemFree'ed when it is no longer needed.
|
|
//***************************************************************************
|
|
void CImap4Agent::PrepareForFetchBody(DWORD dwMsgSeqNum, DWORD dwSizeOfLiteral,
|
|
LPSTR pszBodyTag)
|
|
{
|
|
Assert(0 == m_dwLiteralInProgressBytesLeft);
|
|
|
|
m_fbpFetchBodyPartInProgress.dwMsgSeqNum = dwMsgSeqNum;
|
|
m_fbpFetchBodyPartInProgress.pszBodyTag = pszBodyTag;
|
|
m_fbpFetchBodyPartInProgress.dwTotalBytes = dwSizeOfLiteral;
|
|
m_fbpFetchBodyPartInProgress.dwSizeOfData = 0;
|
|
m_fbpFetchBodyPartInProgress.dwOffset = 0;
|
|
m_fbpFetchBodyPartInProgress.fDone = 0;
|
|
m_fbpFetchBodyPartInProgress.pszData = NULL;
|
|
// Leave the cookies alone, so they persist throughout FETCH response
|
|
|
|
m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral;
|
|
m_irsState = irsFETCH_BODY;
|
|
} // PrepareForFetchBody
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AddBytesToLiteral
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we receive an AE_RECV from the IMAP
|
|
// server while the receiver FSM is in irsLITERAL mode. The caller is
|
|
// expected to call CASyncConn::ReadBytes and update the literal byte-count.
|
|
// This function just handles the buffer-work.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszResponseBuf [in] - the buffer of data returned via
|
|
// CASyncConn::ReadBytes.
|
|
// DWORD dwNumBytesRead [in] - the size of the buffer pointed to by
|
|
// CASyncConn::ReadBytes.
|
|
//***************************************************************************
|
|
void CImap4Agent::AddBytesToLiteral(LPSTR pszResponseBuf, DWORD dwNumBytesRead)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszResponseBuf);
|
|
|
|
if (NULL == m_pilfLiteralInProgress) {
|
|
AssertSz(FALSE, "I'm still in irsLITERAL state, but I'm not set up to recv literals!");
|
|
m_irsState = irsIDLE;
|
|
goto exit;
|
|
}
|
|
|
|
// Find out if this literal will be stored as a string or stream (this
|
|
// decision was made in CheckForCompleteResponse using size of literal).
|
|
Assert(iltLITERAL == m_pilfLiteralInProgress->iltFragmentType);
|
|
if (ilsSTREAM == m_pilfLiteralInProgress->ilsLiteralStoreType) {
|
|
HRESULT hrResult;
|
|
ULONG ulNumBytesWritten;
|
|
|
|
// Store literal as stream
|
|
hrResult = (m_pilfLiteralInProgress->data.pstmSource)->Write(pszResponseBuf,
|
|
dwNumBytesRead, &ulNumBytesWritten);
|
|
Assert(SUCCEEDED(hrResult) && ulNumBytesWritten == dwNumBytesRead);
|
|
}
|
|
else {
|
|
LPSTR pszLiteralStartPoint;
|
|
|
|
// Concatenate literal to literal in progress
|
|
// $REVIEW: Perf enhancement - CALCULATE insertion point
|
|
pszLiteralStartPoint = m_pilfLiteralInProgress->data.pszSource +
|
|
lstrlen(m_pilfLiteralInProgress->data.pszSource);
|
|
Assert(pszLiteralStartPoint + dwNumBytesRead <=
|
|
m_pilfLiteralInProgress->data.pszSource +
|
|
m_pilfLiteralInProgress->dwLengthOfFragment);
|
|
CopyMemory(pszLiteralStartPoint, pszResponseBuf, dwNumBytesRead);
|
|
*(pszLiteralStartPoint + dwNumBytesRead) = '\0'; // Null-terminate
|
|
}
|
|
|
|
// Check for end-of-literal
|
|
if (0 == m_dwLiteralInProgressBytesLeft) {
|
|
// We now have the complete literal! Queue it up and move on
|
|
EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue);
|
|
m_irsState = irsIDLE;
|
|
m_pilfLiteralInProgress = NULL;
|
|
}
|
|
|
|
exit:
|
|
SafeMemFree(pszResponseBuf);
|
|
} // AddBytesToLiteral
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DispatchFetchBodyPart
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever receive a packet which is part of a
|
|
// message body part of a FETCH response. This packet is dispatched to the
|
|
// caller in this function via the OnResponse(irtFETCH_BODY) callback. If
|
|
// the message body part is finished, this function also restores the
|
|
// receiver code to receive lines so that the FETCH response may be completed.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszResponseBuf [in] - a pointer to the packet which is part of
|
|
// the message body part of the current FETCH response.
|
|
// DWORD dwNumBytesRead [in] - the size of the data pointed to by
|
|
// pszResponseBuf.
|
|
// BOOL fFreeBodyTagAtEnd [in] - TRUE if
|
|
// m_fbpFetchBodyPartInProgress.pszBodyTag points to a string dup, in
|
|
// which case it must be MemFree'ed when the message body part is
|
|
// finished. FALSE if the pszBodyTag member must not be MemFree'ed.
|
|
//***************************************************************************
|
|
void CImap4Agent::DispatchFetchBodyPart(LPSTR pszResponseBuf,
|
|
DWORD dwNumBytesRead,
|
|
BOOL fFreeBodyTagAtEnd)
|
|
{
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
AssertSz(0 != m_fbpFetchBodyPartInProgress.dwMsgSeqNum,
|
|
"Are you sure you're set up to receive a Fetch Body Part?");
|
|
|
|
// Update the FETCH body part structure
|
|
m_fbpFetchBodyPartInProgress.dwSizeOfData = dwNumBytesRead;
|
|
m_fbpFetchBodyPartInProgress.pszData = pszResponseBuf;
|
|
m_fbpFetchBodyPartInProgress.fDone =
|
|
(m_fbpFetchBodyPartInProgress.dwOffset + dwNumBytesRead >=
|
|
m_fbpFetchBodyPartInProgress.dwTotalBytes);
|
|
|
|
// Send an IMAP response callback for this body part
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
irIMAPResponse.irtResponseType = irtFETCH_BODY;
|
|
irIMAPResponse.irdResponseData.pFetchBodyPart = &m_fbpFetchBodyPartInProgress;
|
|
AssertSz(S_OK == irIMAPResponse.hrResult,
|
|
"Make sure fDone is TRUE if FAILED(hrResult))");
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
// Update the next buffer's offset
|
|
m_fbpFetchBodyPartInProgress.dwOffset += dwNumBytesRead;
|
|
|
|
// Check for end of body part
|
|
if (m_fbpFetchBodyPartInProgress.dwOffset >=
|
|
m_fbpFetchBodyPartInProgress.dwTotalBytes) {
|
|
|
|
Assert(0 == m_dwLiteralInProgressBytesLeft);
|
|
Assert(TRUE == m_fbpFetchBodyPartInProgress.fDone);
|
|
Assert(m_fbpFetchBodyPartInProgress.dwOffset == m_fbpFetchBodyPartInProgress.dwTotalBytes);
|
|
if (fFreeBodyTagAtEnd)
|
|
MemFree(m_fbpFetchBodyPartInProgress.pszBodyTag);
|
|
|
|
// Enqueue the tombstone literal, if fetch body nstring was sent as literal
|
|
if (NULL != m_pilfLiteralInProgress) {
|
|
EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue);
|
|
m_pilfLiteralInProgress = NULL;
|
|
}
|
|
|
|
// Zero the fetch body part structure, but leave the cookies
|
|
PrepareForFetchBody(0, 0, NULL);
|
|
m_irsState = irsIDLE; // Overrides irsFETCH_BODY set by PrepareForFetchBody
|
|
}
|
|
else {
|
|
Assert(FALSE == m_fbpFetchBodyPartInProgress.fDone);
|
|
}
|
|
} // DispatchFetchBodyPart
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: UploadStreamProgress
|
|
//
|
|
// Purpose:
|
|
// This function sends irtAPPEND_PROGRESS responses to the callback so
|
|
// that the IIMAPTransport user can report the progress of an APPEND command.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwBytesUploaded [in] - number of bytes just uploaded to the
|
|
// server. This function retains a running count of bytes uploaded.
|
|
//***************************************************************************
|
|
void CImap4Agent::UploadStreamProgress(DWORD dwBytesUploaded)
|
|
{
|
|
APPEND_PROGRESS ap;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Check if we should report APPEND upload progress. We report if we are currently executing
|
|
// APPEND and the CRLF is waiting to be sent
|
|
if (NULL == m_piciCmdInSending || icAPPEND_COMMAND != m_piciCmdInSending->icCommandID ||
|
|
NULL == m_piciCmdInSending->pilqCmdLineQueue)
|
|
return;
|
|
else {
|
|
IMAP_LINE_FRAGMENT *pilf = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
|
|
|
|
// It's an APPEND command with non-empty linefrag queue, now check that next
|
|
// linefrag fits description for linefrag after msg body
|
|
if (NULL == pilf || iltLINE != pilf->iltFragmentType ||
|
|
ilsSTRING != pilf->ilsLiteralStoreType || 2 != pilf->dwLengthOfFragment ||
|
|
'\r' != pilf->data.pszSource[0] || '\n' != pilf->data.pszSource[1] ||
|
|
NULL != pilf->pilfNextFragment)
|
|
return;
|
|
}
|
|
|
|
// Report current progress of message upload
|
|
m_dwAppendStreamUploaded += dwBytesUploaded;
|
|
ap.dwUploaded = m_dwAppendStreamUploaded;
|
|
ap.dwTotal = m_dwAppendStreamTotal;
|
|
Assert(0 != ap.dwTotal);
|
|
Assert(ap.dwTotal >= ap.dwUploaded);
|
|
|
|
irIMAPResponse.wParam = m_piciCmdInSending->wParam;
|
|
irIMAPResponse.lParam = m_piciCmdInSending->lParam;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL;
|
|
irIMAPResponse.irtResponseType = irtAPPEND_PROGRESS;
|
|
irIMAPResponse.irdResponseData.papAppendProgress = ≈
|
|
OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse);
|
|
} // UploadStreamProgress
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnNotify
|
|
//
|
|
// Purpose: This function is required for the IAsyncConnCB which we derive
|
|
// from (callback for CAsyncConn class). This function acts on CASyncConn
|
|
// state changes and events.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnNotify(ASYNCSTATE asOld, ASYNCSTATE asNew, ASYNCEVENT ae)
|
|
{
|
|
char szLogFileLine[128];
|
|
|
|
// Check refcount, but exception is that we can get AE_CLOSE. CImap4Agent's
|
|
// destructor calls CASyncConn's Close() member, which generates one last
|
|
// message, the event AE_CLOSE, with m_lRefCount == 0.
|
|
Assert(m_lRefCount > 0 || (0 == m_lRefCount && AE_CLOSE == ae));
|
|
|
|
// Record AsyncConn event/state-change in log file
|
|
wnsprintf(szLogFileLine, ARRAYSIZE(szLogFileLine), "OnNotify: asOld = %d, asNew = %d, ae = %d",
|
|
asOld, asNew, ae);
|
|
if (m_pLogFile)
|
|
m_pLogFile->WriteLog(LOGFILE_DB, szLogFileLine);
|
|
|
|
// Check for disconnect
|
|
if (AS_DISCONNECTED == asNew) {
|
|
m_irsState = irsNOT_CONNECTED;
|
|
m_ssServerState = ssNotConnected;
|
|
m_fIDLE = FALSE;
|
|
m_bFreeToSend = TRUE;
|
|
}
|
|
|
|
// Act on async event
|
|
switch (ae) {
|
|
case AE_RECV: {
|
|
HRESULT hrResult;
|
|
|
|
// Process response lines until no more lines (hrIncomplete result)
|
|
do {
|
|
hrResult = ProcessResponseLine();
|
|
} while (SUCCEEDED(hrResult));
|
|
|
|
// If error is other than IXP_E_INCOMPLETE, drop connection
|
|
if (IXP_E_INCOMPLETE != hrResult) {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
// Looks fatal, better warn the user that disconnection is imminent
|
|
LoadString(g_hLocRes, idsIMAPSocketReadError, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
|
|
|
|
// What else can we do but drop the connection?
|
|
DropConnection();
|
|
} // if error other than IXP_E_INCOMPLETE
|
|
break;
|
|
} // case AE_RECV
|
|
|
|
case AE_SENDDONE:
|
|
UploadStreamProgress(m_pSocket->UlGetSendByteCount());
|
|
|
|
// Received AE_SENDDONE from CAsyncConn class. We are free to send more data
|
|
m_bFreeToSend = TRUE;
|
|
ProcessSendQueue(iseSENDDONE); // Informs them that they may start sending again
|
|
break;
|
|
|
|
case AE_WRITE:
|
|
UploadStreamProgress(m_pSocket->UlGetSendByteCount());
|
|
break;
|
|
|
|
default:
|
|
CIxpBase::OnNotify(asOld, asNew, ae);
|
|
break; // case default
|
|
} // switch (ae)
|
|
} // OnNotify
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ProcessResponseLine
|
|
//
|
|
// Purpose:
|
|
// This functions handles the AE_RECV event of the OnNotify() callback.
|
|
// It gets a response line from the server (if available) and dispatches
|
|
// the line to the proper recipient based on the state of the receiver FSM.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of CAsyncConn line retrieval.
|
|
// hrIncomplete (an error code) is returned if no more complete lines can
|
|
// be retrieved from CAsyncConn's buffer.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ProcessResponseLine(void)
|
|
{
|
|
HRESULT hrASyncResult;
|
|
char *pszResponseBuf;
|
|
int cbRead;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
// We are always in one of two modes: line mode, or byte mode. Figure out which.
|
|
if (irsLITERAL != m_irsState && irsFETCH_BODY != m_irsState) {
|
|
// We're in line mode. Get response line from server
|
|
hrASyncResult = m_pSocket->ReadLine(&pszResponseBuf, &cbRead);
|
|
if (FAILED(hrASyncResult))
|
|
return hrASyncResult;
|
|
|
|
// Record received line in log file
|
|
if (m_pLogFile)
|
|
m_pLogFile->WriteLog(LOGFILE_RX, pszResponseBuf);
|
|
} // if-line mode
|
|
else {
|
|
// We're in literal mode. Get as many bytes as we can.
|
|
hrASyncResult = m_pSocket->ReadBytes(&pszResponseBuf,
|
|
m_dwLiteralInProgressBytesLeft, &cbRead);
|
|
if (FAILED(hrASyncResult))
|
|
return hrASyncResult;
|
|
|
|
// Update our byte count
|
|
Assert((DWORD)cbRead <= m_dwLiteralInProgressBytesLeft);
|
|
m_dwLiteralInProgressBytesLeft -= cbRead;
|
|
|
|
// Make note of received blob in log file
|
|
if (m_pLogFile) {
|
|
char szLogLine[CMDLINE_BUFSIZE];
|
|
|
|
wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Buffer (literal) of length %i", cbRead);
|
|
m_pLogFile->WriteLog(LOGFILE_RX, szLogLine);
|
|
}
|
|
} // else-not line mode
|
|
|
|
// Process it
|
|
switch (m_irsState) {
|
|
case irsUNINITIALIZED:
|
|
AssertSz(FALSE, "Attempted to use Imap4Agent class without initializing");
|
|
SafeMemFree(pszResponseBuf);
|
|
break;
|
|
|
|
case irsNOT_CONNECTED:
|
|
AssertSz(FALSE, "Received response from server when not connected");
|
|
SafeMemFree(pszResponseBuf);
|
|
break;
|
|
|
|
case irsSVR_GREETING:
|
|
ProcessServerGreeting(pszResponseBuf, cbRead);
|
|
break;
|
|
|
|
case irsIDLE: {
|
|
IMAP_RESPONSE_ID irParseResult;
|
|
|
|
CheckForCompleteResponse(pszResponseBuf, cbRead, &irParseResult);
|
|
|
|
// Check for unsolicited BYE response, and notify user of error
|
|
// Solicited BYE responses (eg, during LOGOUT cmd) can be ignored
|
|
if (irBYE_RESPONSE == irParseResult &&
|
|
IXP_AUTHRETRY != m_status &&
|
|
IXP_DISCONNECTING != m_status &&
|
|
IXP_DISCONNECTED != m_status) {
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
// Looks like an unsolicited BYE response to me
|
|
// Drop connection to avoid IXP_E_CONNECTION_DROPPED err
|
|
DropConnection();
|
|
|
|
// Report to user (sometimes server provides useful error text)
|
|
LoadString(g_hLocRes, idsIMAPUnsolicitedBYE, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
OnIMAPError(IXP_E_IMAP_UNSOLICITED_BYE, szFailureText,
|
|
USE_LAST_RESPONSE);
|
|
}
|
|
} // case irsIDLE
|
|
break;
|
|
|
|
case irsLITERAL:
|
|
AddBytesToLiteral(pszResponseBuf, cbRead);
|
|
break;
|
|
|
|
case irsFETCH_BODY:
|
|
DispatchFetchBodyPart(pszResponseBuf, cbRead, fFREE_BODY_TAG);
|
|
SafeMemFree(pszResponseBuf);
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE, "Unhandled receiver state in ProcessResponseLine()");
|
|
SafeMemFree(pszResponseBuf);
|
|
break;
|
|
} // switch (m_irsState)
|
|
|
|
return hrASyncResult;
|
|
} // ProcessResponseLine
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ProcessSendQueue
|
|
//
|
|
// Purpose:
|
|
// This function is responsible for all transmissions from the client to
|
|
// the IMAP server. It is called when certain events occur, such as the
|
|
// receipt of the AE_SENDDONE event in OnNotify().
|
|
//
|
|
// Arguments:
|
|
// IMAP_SEND_EVENT iseEvent [in] - the send event which just occurred,
|
|
// such as iseSEND_COMMAND (used to initiate a command) or
|
|
// iseCMD_CONTINUATION (when command continuation response received from
|
|
// the IMAP server).
|
|
//***************************************************************************
|
|
void CImap4Agent::ProcessSendQueue(IMAP_SEND_EVENT iseEvent)
|
|
{
|
|
boolean bFreeToSendLiteral, bFreeToUnpause;
|
|
IMAP_LINE_FRAGMENT *pilfNextFragment;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(ssNotConnected < m_ssServerState);
|
|
Assert(irsNOT_CONNECTED < m_irsState);
|
|
|
|
// Initialize variables
|
|
bFreeToSendLiteral = FALSE;
|
|
bFreeToUnpause = FALSE;
|
|
|
|
// Peek at current fragment
|
|
EnterCriticalSection(&m_cs); // Reserve this NOW to avoid deadlock
|
|
EnterCriticalSection(&m_csSendQueue);
|
|
GetNextCmdToSend();
|
|
if (NULL != m_piciCmdInSending)
|
|
pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
|
|
else
|
|
pilfNextFragment = NULL;
|
|
|
|
// Act on the IMAP send event posted to us
|
|
switch (iseEvent) {
|
|
case iseSEND_COMMAND:
|
|
case iseSENDDONE:
|
|
// We don't have to do anything special for these events
|
|
break;
|
|
|
|
case iseCMD_CONTINUATION:
|
|
// Received command continuation from IMAP server. We are free to send literal
|
|
bFreeToSendLiteral = TRUE;
|
|
Assert(NULL != pilfNextFragment &&
|
|
iltLITERAL == pilfNextFragment->iltFragmentType);
|
|
break;
|
|
|
|
case iseUNPAUSE:
|
|
bFreeToUnpause = TRUE;
|
|
IxpAssert(NULL != pilfNextFragment &&
|
|
iltPAUSE == pilfNextFragment->iltFragmentType);
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE, "Received unknown IMAP send event");
|
|
break;
|
|
}
|
|
|
|
|
|
// Send as many fragments as we can. We must stop sending if:
|
|
// a) Any AsyncConn send command returns hrWouldBlock.
|
|
// b) The send queue is empty
|
|
// c) Next fragment is a literal and we don't have cmd continuation from svr
|
|
// d) We are at a iltPAUSE fragment, and we don't have the go-ahead to unpause
|
|
// e) We are at a iltSTOP fragment.
|
|
while (TRUE == m_bFreeToSend && NULL != pilfNextFragment &&
|
|
((iltLITERAL != pilfNextFragment->iltFragmentType) || TRUE == bFreeToSendLiteral) &&
|
|
((iltPAUSE != pilfNextFragment->iltFragmentType) || TRUE == bFreeToUnpause) &&
|
|
(iltSTOP != pilfNextFragment->iltFragmentType))
|
|
{
|
|
HRESULT hrResult;
|
|
int iNumBytesSent;
|
|
IMAP_LINE_FRAGMENT *pilf;
|
|
|
|
// We are free to send the next fragment, whether it's a line, literal or rangelist
|
|
// Put us into busy mode to enable the watchdog timer
|
|
if (FALSE == m_fBusy) {
|
|
hrResult = HrEnterBusy();
|
|
Assert(SUCCEEDED(hrResult));
|
|
// In retail, we want to try to continue even if HrEnterBusy failed.
|
|
}
|
|
|
|
// Send next fragment (have to check if stored as a string or a stream)
|
|
pilfNextFragment = pilfNextFragment->pilfNextFragment; // Peek at next frag
|
|
pilf = DequeueFragment(m_piciCmdInSending->pilqCmdLineQueue); // Get current frag
|
|
if (iltPAUSE == pilf->iltFragmentType) {
|
|
hrResult = S_OK; // Do nothing
|
|
}
|
|
else if (iltSTOP == pilf->iltFragmentType) {
|
|
AssertSz(FALSE, "What are we doing trying to process a STOP?");
|
|
hrResult = S_OK; // Do nothing
|
|
}
|
|
else if (iltRANGELIST == pilf->iltFragmentType) {
|
|
AssertSz(FALSE, "All rangelists should have been coalesced!");
|
|
hrResult = S_OK; // Do nothing
|
|
}
|
|
else if (ilsSTRING == pilf->ilsLiteralStoreType) {
|
|
hrResult = m_pSocket->SendBytes(pilf->data.pszSource,
|
|
pilf->dwLengthOfFragment, &iNumBytesSent);
|
|
|
|
// Record sent line in log file
|
|
if (m_pLogFile) {
|
|
// Hide the LOGIN command from logfile, for security reasons
|
|
if (icLOGIN_COMMAND != m_piciCmdInSending->icCommandID)
|
|
m_pLogFile->WriteLog(LOGFILE_TX, pilf->data.pszSource);
|
|
else
|
|
m_pLogFile->WriteLog(LOGFILE_TX, "LOGIN command sent");
|
|
}
|
|
}
|
|
else if (ilsSTREAM == pilf->ilsLiteralStoreType) {
|
|
char szLogLine[128];
|
|
|
|
// No need to rewind stream - CAsyncConn::SendStream does it for us
|
|
hrResult = m_pSocket->SendStream(pilf->data.pstmSource, &iNumBytesSent);
|
|
|
|
// Record stream size in log file
|
|
wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Stream of length %lu", pilf->dwLengthOfFragment);
|
|
if (m_pLogFile)
|
|
m_pLogFile->WriteLog(LOGFILE_TX, szLogLine);
|
|
|
|
// Record stream size for progress indication
|
|
if (icAPPEND_COMMAND == m_piciCmdInSending->icCommandID) {
|
|
m_dwAppendStreamUploaded = 0;
|
|
m_dwAppendStreamTotal = pilf->dwLengthOfFragment;
|
|
UploadStreamProgress(iNumBytesSent);
|
|
}
|
|
}
|
|
else {
|
|
AssertSz(FALSE, "What is in my send queue?");
|
|
hrResult = S_OK; // Ignore it and try to continue
|
|
}
|
|
|
|
// Clean up variables after the send
|
|
bFreeToSendLiteral = FALSE; // We've used up the cmd continuation
|
|
bFreeToUnpause = FALSE; // We've used this up, too
|
|
FreeFragment(&pilf);
|
|
|
|
// Handle errors in sending.
|
|
// If either send command returns hrWouldBlock, this means we cannot send
|
|
// more data until we receive an AE_SENDDONE event from CAsyncConn.
|
|
if (IXP_E_WOULD_BLOCK == hrResult) {
|
|
m_bFreeToSend = FALSE;
|
|
hrResult = S_OK; // $REVIEW: TEMPORARY until EricAn makes hrWouldBlock a success code
|
|
}
|
|
else if (FAILED(hrResult)) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
char szFailureText[MAX_RESOURCESTRING];
|
|
|
|
// Send error: Report this command as terminated
|
|
irIMAPResponse.wParam = m_piciCmdInSending->wParam;
|
|
irIMAPResponse.lParam = m_piciCmdInSending->lParam;
|
|
irIMAPResponse.hrResult = hrResult;
|
|
LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
|
|
ARRAYSIZE(szFailureText));
|
|
irIMAPResponse.lpszResponseText = szFailureText;
|
|
irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
|
|
OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
|
|
// Are we finished with the current command?
|
|
if (NULL == pilfNextFragment || FAILED(hrResult)) {
|
|
CIMAPCmdInfo *piciFinishedCmd;
|
|
|
|
// Dequeue current command from send queue
|
|
piciFinishedCmd = DequeueCommand();
|
|
if (NULL != piciFinishedCmd) {
|
|
if (SUCCEEDED(hrResult)) {
|
|
// We successfully finished sending current command. Put it in
|
|
// list of commands waiting for a server response
|
|
AddPendingCommand(piciFinishedCmd);
|
|
Assert(NULL == pilfNextFragment);
|
|
}
|
|
else {
|
|
// Failed commands don't deserve to live
|
|
delete piciFinishedCmd;
|
|
pilfNextFragment = NULL; // No longer valid
|
|
|
|
// Drop out of busy mode
|
|
AssertSz(m_fBusy, "Check your logic, I'm calling LeaveBusy "
|
|
"although not in a busy state!");
|
|
LeaveBusy();
|
|
}
|
|
}
|
|
else {
|
|
// Hey, someone pulled the rug out!
|
|
AssertSz(FALSE, "I had this cmd... and now it's GONE!");
|
|
}
|
|
} // if (NULL == pilfNextFragment || FAILED(hrResult))
|
|
|
|
|
|
// If we finished sending current cmd, set us up to send next command
|
|
if (NULL == pilfNextFragment && NULL != m_piciSendQueue) {
|
|
GetNextCmdToSend();
|
|
if (NULL != m_piciCmdInSending)
|
|
pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
|
|
}
|
|
|
|
} // while
|
|
|
|
LeaveCriticalSection(&m_csSendQueue);
|
|
LeaveCriticalSection(&m_cs);
|
|
} // ProcessSendQueue
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetNextCmdToSend
|
|
//
|
|
// Purpose:
|
|
// This function leaves a pointer to the next command to send, in
|
|
// m_piciCmdInSending. If m_piciCmdInSending is already non-NULL (indicating
|
|
// a command in progress), then this function does nothing. Otherwise, this
|
|
// function chooses the next command from m_piciSendQueue using a set of
|
|
// rules described within.
|
|
//***************************************************************************
|
|
void CImap4Agent::GetNextCmdToSend(void)
|
|
{
|
|
CIMAPCmdInfo *pici;
|
|
|
|
// First check if we're connected
|
|
if (IXP_CONNECTED != m_status &&
|
|
IXP_AUTHORIZING != m_status &&
|
|
IXP_AUTHRETRY != m_status &&
|
|
IXP_AUTHORIZED != m_status &&
|
|
IXP_DISCONNECTING != m_status) {
|
|
Assert(NULL == m_piciCmdInSending);
|
|
return;
|
|
}
|
|
|
|
// Check if we're already in the middle of sending a command
|
|
if (NULL != m_piciCmdInSending)
|
|
return;
|
|
|
|
// Loop through the send queue looking for next eligible candidate to send
|
|
pici = m_piciSendQueue;
|
|
while (NULL != pici) {
|
|
IMAP_COMMAND icCurrentCmd;
|
|
|
|
// For a command to be sent, it must meet the following criteria:
|
|
// (1) The server must be in the correct server state. Authenticated cmds such
|
|
// as SELECT must wait until non-Authenticated cmds like LOGIN are complete.
|
|
// (2) Commands for which we want to guarantee proper wParam, lParam for their
|
|
// untagged responses cannot be streamed. See CanStreamCommand for details.
|
|
// (3) If the command is NON-UID FETCH/STORE/SEARCH or COPY, then all pending
|
|
// cmds must be NON-UID FETCH/STORE/SEARCH.
|
|
|
|
icCurrentCmd = pici->icCommandID;
|
|
if (m_ssServerState >= pici->ssMinimumState && CanStreamCommand(icCurrentCmd)) {
|
|
if ((icFETCH_COMMAND == icCurrentCmd || icSTORE_COMMAND == icCurrentCmd ||
|
|
icSEARCH_COMMAND == icCurrentCmd || icCOPY_COMMAND == icCurrentCmd) &&
|
|
FALSE == pici->fUIDRangeList) {
|
|
if (isValidNonWaitingCmdSequence())
|
|
break; // This command is good to go
|
|
}
|
|
else
|
|
break; // This command is good to go
|
|
}
|
|
|
|
// Advance ptr to next command
|
|
pici = pici->piciNextCommand;
|
|
} // while
|
|
|
|
// If we found a command, coalesce its iltLINE and iltRANGELIST elements
|
|
if (NULL != pici) {
|
|
CompressCommand(pici);
|
|
m_piciCmdInSending = pici;
|
|
}
|
|
} // GetNextCmdToSend
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: CanStreamCommand
|
|
//
|
|
// Purpose:
|
|
// This function determines whether or not the given command can be
|
|
// streamed. All commands can be streamed except for the following:
|
|
// SELECT, EXAMINE, LIST, LSUB and SEARCH.
|
|
//
|
|
// SELECT and EXAMINE cannot be streamed because it doesn't make much sense
|
|
// to allow that.
|
|
// LIST, LSUB and SEARCH cannot be streamed because we want to guarantee
|
|
// that we can identify the correct wParam, lParam and pCBHandler when we call
|
|
// OnResponse for their untagged responses.
|
|
//
|
|
// Arguments:
|
|
// IMAP_COMMAND icCommandID [in] - the command which you would like to send
|
|
// to the server.
|
|
//
|
|
// Returns:
|
|
// TRUE if the given command may be sent. FALSE if you cannot send the
|
|
// given command at this time (try again later).
|
|
//***************************************************************************
|
|
boolean CImap4Agent::CanStreamCommand(IMAP_COMMAND icCommandID)
|
|
{
|
|
boolean fResult;
|
|
WORD wNumberOfMatches;
|
|
|
|
fResult = TRUE;
|
|
wNumberOfMatches = 0;
|
|
switch (icCommandID) {
|
|
// We don't stream any of the following commands
|
|
|
|
case icSELECT_COMMAND:
|
|
case icEXAMINE_COMMAND:
|
|
wNumberOfMatches = FindTransactionID(NULL, NULL, NULL,
|
|
icSELECT_COMMAND, icEXAMINE_COMMAND);
|
|
break;
|
|
|
|
case icLIST_COMMAND:
|
|
wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLIST_COMMAND);
|
|
break;
|
|
|
|
case icLSUB_COMMAND:
|
|
wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLSUB_COMMAND);
|
|
break;
|
|
|
|
case icSEARCH_COMMAND:
|
|
wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icSEARCH_COMMAND);
|
|
break;
|
|
} //switch
|
|
|
|
if (wNumberOfMatches > 0)
|
|
fResult = FALSE;
|
|
|
|
return fResult;
|
|
} // CanStreamCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isValidNonWaitingCmdSequence
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we would like to send a FETCH, STORE,
|
|
// SEARCH or COPY command (all NON-UID) to the server. These commands are
|
|
// subject to waiting rules as discussed in section 5.5 of RFC2060.
|
|
//
|
|
// Returns:
|
|
// TRUE if the non-UID FETCH/STORE/SEARCH/COPY command can be sent at
|
|
// this time. FALSE if the command cannot be sent at this time (try again
|
|
// later).
|
|
//***************************************************************************
|
|
boolean CImap4Agent::isValidNonWaitingCmdSequence(void)
|
|
{
|
|
CIMAPCmdInfo *pici;
|
|
boolean fResult;
|
|
|
|
// Loop through the list of pending commands
|
|
pici = m_piciPendingList;
|
|
fResult = TRUE;
|
|
while (NULL != pici) {
|
|
IMAP_COMMAND icCurrentCmd;
|
|
|
|
// non-UID FETCH/STORE/SEARCH/COPY can only execute if the only
|
|
// pending commands are non-UID FETCH/STORE/SEARCH.
|
|
icCurrentCmd = pici->icCommandID;
|
|
if (icFETCH_COMMAND != icCurrentCmd &&
|
|
icSTORE_COMMAND != icCurrentCmd &&
|
|
icSEARCH_COMMAND != icCurrentCmd ||
|
|
pici->fUIDRangeList) {
|
|
fResult = FALSE;
|
|
break;
|
|
}
|
|
|
|
// Advance pointer to next command
|
|
pici = pici->piciNextCommand;
|
|
} // while
|
|
|
|
return fResult;
|
|
} // isValidNonWaitingCmdSequence
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: CompressCommand
|
|
//
|
|
// Purpose: This function walks through the given command's linefrag queue
|
|
// and combines all sequential iltLINE and iltRANGELIST linefrag elements
|
|
// into a single iltLINE element for transmitting purposes. The reason we
|
|
// have to combine these is because I had a pipe dream once that CImap4Agent
|
|
// would auto-detect EXPUNGE responses and modify all iltRANGELIST elements
|
|
// in m_piciSendQueue to reflect the new msg seq num reality. Who knows,
|
|
// it might even come true some day.
|
|
//
|
|
// When it does come true, this function can still exist: once a command
|
|
// enters m_piciCmdInSending, it is too late to modify its rangelist.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *pici [in] - pointer to the IMAP command to compress.
|
|
//***************************************************************************
|
|
void CImap4Agent::CompressCommand(CIMAPCmdInfo *pici)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfCurrent, *pilfStartOfRun, *pilfPreStartOfRun;
|
|
HRESULT hrResult;
|
|
|
|
// Codify assumptions
|
|
Assert(NULL != pici);
|
|
Assert(5 == iltLAST); // If this changes, update this function
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
pilfCurrent = pici->pilqCmdLineQueue->pilfFirstFragment;
|
|
pilfStartOfRun = pilfCurrent;
|
|
pilfPreStartOfRun = NULL; // Points to linefrag element before pilfStartOfRun
|
|
while (1) {
|
|
if (NULL == pilfCurrent ||
|
|
(iltLINE != pilfCurrent->iltFragmentType &&
|
|
iltRANGELIST != pilfCurrent->iltFragmentType)) {
|
|
// We've hit a non-coalescable linefrag, coalesce previous run
|
|
// We only coalesce runs which are greater than one linefrag element
|
|
if (NULL != pilfStartOfRun && pilfCurrent != pilfStartOfRun->pilfNextFragment) {
|
|
IMAP_LINE_FRAGMENT *pilf, *pilfSuperLine;
|
|
CByteStream bstmCmdLine;
|
|
|
|
// Run length > 1, coalesce the entire run
|
|
pilf = pilfStartOfRun;
|
|
while (pilf != pilfCurrent) {
|
|
if (iltLINE == pilf->iltFragmentType) {
|
|
hrResult = bstmCmdLine.Write(pilf->data.pszSource,
|
|
pilf->dwLengthOfFragment, NULL);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
}
|
|
else {
|
|
LPSTR pszMsgRange;
|
|
DWORD dwLengthOfString;
|
|
|
|
// Convert rangelist to string
|
|
Assert(iltRANGELIST == pilf->iltFragmentType);
|
|
hrResult = pilf->data.prlRangeList->
|
|
RangeToIMAPString(&pszMsgRange, &dwLengthOfString);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = bstmCmdLine.Write(pszMsgRange, dwLengthOfString, NULL);
|
|
MemFree(pszMsgRange);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Append a space behind the rangelist
|
|
hrResult = bstmCmdLine.Write(g_szSpace, 1, NULL);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
} // else
|
|
|
|
pilf = pilf->pilfNextFragment;
|
|
} // while (pilf != pilfCurrent)
|
|
|
|
// OK, now we've coalesced the run data into a stream
|
|
// Create a iltLINE fragment to hold the super-string
|
|
pilfSuperLine = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfSuperLine) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
pilfSuperLine->iltFragmentType = iltLINE;
|
|
pilfSuperLine->ilsLiteralStoreType = ilsSTRING;
|
|
hrResult = bstmCmdLine.HrAcquireStringA(&pilfSuperLine->dwLengthOfFragment,
|
|
&pilfSuperLine->data.pszSource, ACQ_DISPLACE);
|
|
if (FAILED(hrResult)) {
|
|
delete pilfSuperLine;
|
|
goto exit;
|
|
}
|
|
|
|
// OK, we've created the uber-line, now link it into list
|
|
pilfSuperLine->pilfNextFragment = pilfCurrent;
|
|
pilfSuperLine->pilfPrevFragment = pilfPreStartOfRun;
|
|
Assert(pilfPreStartOfRun == pilfStartOfRun->pilfPrevFragment);
|
|
if (NULL == pilfPreStartOfRun)
|
|
// Insert at head of queue
|
|
pici->pilqCmdLineQueue->pilfFirstFragment = pilfSuperLine;
|
|
else
|
|
pilfPreStartOfRun->pilfNextFragment = pilfSuperLine;
|
|
|
|
// Special case: if pilfCurrent is NULL, pilfSuperLine is new last frag
|
|
if (NULL == pilfCurrent)
|
|
pici->pilqCmdLineQueue->pilfLastFragment = pilfSuperLine;
|
|
|
|
// Free the old run of linefrag elements
|
|
pilf = pilfStartOfRun;
|
|
while(pilf != pilfCurrent) {
|
|
IMAP_LINE_FRAGMENT *pilfNext;
|
|
|
|
pilfNext = pilf->pilfNextFragment;
|
|
FreeFragment(&pilf);
|
|
pilf = pilfNext;
|
|
} // while(pilf != pilfCurrent)
|
|
} // if run length > 1
|
|
|
|
// Start collecting line fragments for next coalescing run
|
|
if (NULL != pilfCurrent) {
|
|
pilfStartOfRun = pilfCurrent->pilfNextFragment;
|
|
pilfPreStartOfRun = pilfCurrent;
|
|
} // if
|
|
} // if current linefrag is non-coalescable
|
|
|
|
// Advance to next line fragment
|
|
if (NULL != pilfCurrent)
|
|
pilfCurrent = pilfCurrent->pilfNextFragment;
|
|
else
|
|
break; // Our work here is done
|
|
} // while (NULL != pilfCurrent)
|
|
|
|
exit:
|
|
AssertSz(SUCCEEDED(hrResult), "Could not compress an IMAP command");
|
|
} // CompressCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SendCmdLine
|
|
//
|
|
// Purpose:
|
|
// This function enqueues an IMAP line fragment (as opposed to an IMAP
|
|
// literal fragment) on the send queue of the given CIMAPCmdInfo structure.
|
|
// The insertion point can either be in front
|
|
// All IMAP commands are constructed in full before being submitted to the
|
|
// send machinery, so this function does not actually transmit anything.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
|
|
// the IMAP command currently under construction.
|
|
// DWORD dwFlags [in] - various options:
|
|
// sclINSERT_BEFORE_PAUSE: line fragment will be inserted before the
|
|
// first iltPAUSE fragment in the queue. It is the
|
|
// caller's responsibility to ensure that a iltPAUSE
|
|
// fragment exists.
|
|
// sclAPPEND_TO_END: (DEFAULT CASE, there is no flag for this) line fragment
|
|
// will be appended to the end of the queue.
|
|
// sclAPPEND_CRLF: Appends CRLF to contents of lpszCommandText when
|
|
// constructing the line fragment.
|
|
//
|
|
// LPCSTR lpszCommandText [in] - a pointer to the line fragment to enqueue.
|
|
// The first line fragment of all commands should include a tag. This
|
|
// function does not provide command tags, and does not append CRLF to
|
|
// the end of each line by default (see sclAPPEND_CRLF above).
|
|
// DWORD dwCmdLineLength [in] - the length of the text pointed to by
|
|
// lpszCommandText.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SendCmdLine(CIMAPCmdInfo *piciCommand, DWORD dwFlags,
|
|
LPCSTR lpszCommandText, DWORD dwCmdLineLength)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfLine;
|
|
BOOL bResult;
|
|
BOOL fAppendCRLF;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
Assert(NULL != lpszCommandText);
|
|
|
|
// Create and fill out a line fragment element
|
|
fAppendCRLF = !!(dwFlags & sclAPPEND_CRLF);
|
|
pilfLine = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfLine)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfLine->iltFragmentType = iltLINE;
|
|
pilfLine->ilsLiteralStoreType = ilsSTRING;
|
|
pilfLine->dwLengthOfFragment = dwCmdLineLength + (fAppendCRLF ? 2 : 0);
|
|
pilfLine->pilfNextFragment = NULL;
|
|
pilfLine->pilfPrevFragment = NULL;
|
|
DWORD cchSize = (pilfLine->dwLengthOfFragment + 1); // Room for null-term
|
|
bResult = MemAlloc((void **)&pilfLine->data.pszSource, cchSize * sizeof(pilfLine->data.pszSource[0]));
|
|
if (FALSE == bResult)
|
|
{
|
|
delete pilfLine;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
CopyMemory(pilfLine->data.pszSource, lpszCommandText, dwCmdLineLength);
|
|
if (fAppendCRLF)
|
|
StrCpyN(pilfLine->data.pszSource + dwCmdLineLength, c_szCRLF, cchSize - dwCmdLineLength);
|
|
else
|
|
*(pilfLine->data.pszSource + dwCmdLineLength) = '\0'; // Null-terminate the line
|
|
|
|
// Queue it up
|
|
if (dwFlags & sclINSERT_BEFORE_PAUSE) {
|
|
InsertFragmentBeforePause(pilfLine, piciCommand->pilqCmdLineQueue);
|
|
ProcessSendQueue(iseSEND_COMMAND); // Pump send queue in this case
|
|
}
|
|
else
|
|
EnqueueFragment(pilfLine, piciCommand->pilqCmdLineQueue);
|
|
|
|
return S_OK;
|
|
} // SendCmdLine
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SendLiteral
|
|
//
|
|
// Purpose:
|
|
// This function enqueues an IMAP literal fragment (as opposed to an IMAP
|
|
// line fragment) on the send queue of the given CIMAPCmdInfo structure.
|
|
// All IMAP commands are constructed in full before being submitted to the
|
|
// send machinery, so this function does not actually transmit anything.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
|
|
// the IMAP command currently under construction.
|
|
// LPSTREAM pstmLiteral [in] - a pointer to the stream containing the
|
|
// literal to be sent.
|
|
// DWORD dwSizeOfStream [in] - the size of the stream.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SendLiteral(CIMAPCmdInfo *piciCommand,
|
|
LPSTREAM pstmLiteral, DWORD dwSizeOfStream)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfLiteral;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pstmLiteral);
|
|
|
|
// Create and fill out a fragment structure for the literal
|
|
pilfLiteral = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfLiteral)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfLiteral->iltFragmentType = iltLITERAL;
|
|
pilfLiteral->ilsLiteralStoreType = ilsSTREAM;
|
|
pilfLiteral->dwLengthOfFragment = dwSizeOfStream;
|
|
pstmLiteral->AddRef(); // We're about to make a copy of this
|
|
pilfLiteral->data.pstmSource = pstmLiteral;
|
|
pilfLiteral->pilfNextFragment = NULL;
|
|
pilfLiteral->pilfPrevFragment = NULL;
|
|
|
|
// Queue it up to send out when we receive command continuation from svr
|
|
EnqueueFragment(pilfLiteral, piciCommand->pilqCmdLineQueue);
|
|
return S_OK;
|
|
} // SendLiteral
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SendRangelist
|
|
//
|
|
// Purpose:
|
|
// This function enqueues a rangelist on the send queue of the given
|
|
// CIMAPCmdInfo structure. All IMAP commands are constructed in full before
|
|
// being submitted to the send machinery, so this function does not actually
|
|
// transmit anything. The reason for storing rangelists is so that if the
|
|
// rangelist represents a message sequence number range, we can resequence it
|
|
// if we receive EXPUNGE responses before the command is transmitted.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
|
|
// the IMAP command currently under construction.
|
|
// IRangeList *prlRangeList [in] - rangelist which will be converted to an
|
|
// IMAP message set during command transmission.
|
|
// boolean bUIDRangeList [in] - TRUE if rangelist represents a UID message
|
|
// set, FALSE if it represents a message sequence number message set.
|
|
// UID message sets are not subject to resequencing after an EXPUNGE
|
|
// response is received from the server.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SendRangelist(CIMAPCmdInfo *piciCommand,
|
|
IRangeList *prlRangeList, boolean bUIDRangeList)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfRangelist;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
Assert(NULL != prlRangeList);
|
|
|
|
// Create and fill out a rangelist element
|
|
pilfRangelist = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfRangelist)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfRangelist->iltFragmentType = iltRANGELIST;
|
|
pilfRangelist->ilsLiteralStoreType = ilsSTRING;
|
|
pilfRangelist->dwLengthOfFragment = 0;
|
|
pilfRangelist->pilfNextFragment = NULL;
|
|
pilfRangelist->pilfPrevFragment = NULL;
|
|
prlRangeList->AddRef();
|
|
pilfRangelist->data.prlRangeList = prlRangeList;
|
|
|
|
// Queue it up
|
|
EnqueueFragment(pilfRangelist, piciCommand->pilqCmdLineQueue);
|
|
return S_OK;
|
|
} // SendRangelist
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SendPause
|
|
//
|
|
// Purpose:
|
|
// This function enqueues a pause on the send queue of the given
|
|
// CIMAPCmdInfo structure. All IMAP commands are constructed in full before
|
|
// being submitted to the send machinery, so this function does not actually
|
|
// transmit anything. A pause is used to freeze the send queue until we signal
|
|
// it to proceed again. It is used in commands which involve bi-directional
|
|
// communication, such as AUTHENTICATE or the IDLE extension.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
|
|
// the IMAP command currently under construction.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SendPause(CIMAPCmdInfo *piciCommand)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfPause;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
|
|
// Create and fill out a pause element
|
|
pilfPause = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfPause)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfPause->iltFragmentType = iltPAUSE;
|
|
pilfPause->ilsLiteralStoreType = ilsSTRING;
|
|
pilfPause->dwLengthOfFragment = 0;
|
|
pilfPause->pilfNextFragment = NULL;
|
|
pilfPause->pilfPrevFragment = NULL;
|
|
pilfPause->data.pszSource = NULL;
|
|
|
|
// Queue it up
|
|
EnqueueFragment(pilfPause, piciCommand->pilqCmdLineQueue);
|
|
return S_OK;
|
|
} // SendPause
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SendStop
|
|
//
|
|
// Purpose:
|
|
// This function enqueues a STOP on the send queue of the given
|
|
// CIMAPCmdInfo structure. All IMAP commands are constructed in full before
|
|
// being submitted to the send machinery, so this function does not actually
|
|
// transmit anything. A STOP is used to freeze the send queue until that
|
|
// command is removed from the send queue by tagged command completion.
|
|
// Currently only used in the IDLE command, because we don't want to send
|
|
// any commands until the server indicates that we are out of IDLE mode.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
|
|
// the IMAP command currently under construction.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SendStop(CIMAPCmdInfo *piciCommand)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfStop;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
|
|
// Create and fill out a stop element
|
|
pilfStop = new IMAP_LINE_FRAGMENT;
|
|
if (NULL == pilfStop)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pilfStop->iltFragmentType = iltSTOP;
|
|
pilfStop->ilsLiteralStoreType = ilsSTRING;
|
|
pilfStop->dwLengthOfFragment = 0;
|
|
pilfStop->pilfNextFragment = NULL;
|
|
pilfStop->pilfPrevFragment = NULL;
|
|
pilfStop->data.pszSource = NULL;
|
|
|
|
// Queue it up
|
|
EnqueueFragment(pilfStop, piciCommand->pilqCmdLineQueue);
|
|
return S_OK;
|
|
} // SendStop
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseSvrResponseLine
|
|
//
|
|
// Purpose:
|
|
// Given a line, this function classifies the line
|
|
// as an untagged response, a command continuation, or a tagged response.
|
|
// Depending on the classification, the line is then dispatched to helper
|
|
// functions to parse and act on the line.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - IMAP line fragment to parse.
|
|
// The given pointer is updated so that it always points to the last
|
|
// IMAP line fragment processed. The caller need only free this fragment.
|
|
// All previous fragments will have already been freed.
|
|
// boolean *lpbTaggedResponse [out] - sets to TRUE if response is tagged.
|
|
// LPSTR lpszTagFromSvr [out] - returns tag here if response was tagged.
|
|
// IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
|
|
// if we recognized it. Otherwise returns irNONE.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseSvrResponseLine (IMAP_LINE_FRAGMENT **ppilfLine,
|
|
boolean *lpbTaggedResponse,
|
|
LPSTR lpszTagFromSvr,
|
|
IMAP_RESPONSE_ID *pirParseResult)
|
|
{
|
|
LPSTR p, lpszSvrResponseLine;
|
|
HRESULT hrResult;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != lpbTaggedResponse);
|
|
Assert(NULL != lpszTagFromSvr);
|
|
Assert(NULL != pirParseResult);
|
|
|
|
*lpbTaggedResponse = FALSE; // Assume untagged response to start
|
|
*pirParseResult = irNONE;
|
|
|
|
// Make sure we have a line fragment, not a literal
|
|
if (iltLINE != (*ppilfLine)->iltFragmentType) {
|
|
AssertSz(FALSE, "I was passed a literal to parse!");
|
|
return IXP_E_IMAP_RECVR_ERROR;
|
|
}
|
|
else
|
|
lpszSvrResponseLine = (*ppilfLine)->data.pszSource;
|
|
|
|
// Determine if server response was command continuation, untagged or tagged
|
|
// Look at first character of response line to figure it out
|
|
hrResult = S_OK;
|
|
p = lpszSvrResponseLine + 1;
|
|
switch(*lpszSvrResponseLine) {
|
|
|
|
case cCOMMAND_CONTINUATION_PREFIX:
|
|
if (NULL != m_piciCmdInSending &&
|
|
icAUTHENTICATE_COMMAND == m_piciCmdInSending->icCommandID) {
|
|
LPSTR pszStartOfData;
|
|
DWORD dwLengthOfData;
|
|
|
|
if ((*ppilfLine)->dwLengthOfFragment <= 2) {
|
|
pszStartOfData = NULL;
|
|
dwLengthOfData = 0;
|
|
}
|
|
else {
|
|
pszStartOfData = p + 1;
|
|
dwLengthOfData = (*ppilfLine)->dwLengthOfFragment - 2;
|
|
}
|
|
AuthenticateUser(aeCONTINUE, pszStartOfData, dwLengthOfData);
|
|
}
|
|
else if (NULL != m_piciCmdInSending &&
|
|
icIDLE_COMMAND == m_piciCmdInSending->icCommandID) {
|
|
// Leave busy mode, as we may be sitting idle for some time
|
|
LeaveBusy();
|
|
m_fIDLE = TRUE; // We are now in IDLE mode
|
|
|
|
// Check if any commands are waiting to be sent
|
|
if ((NULL != m_piciCmdInSending) && (NULL != m_piciCmdInSending->piciNextCommand))
|
|
ProcessSendQueue(iseUNPAUSE); // Let's get out of IDLE
|
|
}
|
|
else {
|
|
// Literal continuation response
|
|
// Save response text - assume space follows "+", no big deal if it doesn't
|
|
StrCpyN(m_szLastResponseText, p + 1, ARRAYSIZE(m_szLastResponseText));
|
|
ProcessSendQueue(iseCMD_CONTINUATION); // Go ahead and send the literal
|
|
}
|
|
|
|
break; // case cCOMMAND_CONTINUATION_PREFIX
|
|
|
|
|
|
case cUNTAGGED_RESPONSE_PREFIX:
|
|
if (cSPACE == *p) {
|
|
// Server response fits spec'd form, parse as untagged response
|
|
|
|
p += 1; // Advance p to point to next word
|
|
|
|
// Untagged responses can be status, server/mailbox status or
|
|
// message status responses.
|
|
|
|
// Check for message status responses, first, by seeing
|
|
// if first char of next word is a number
|
|
// *** Consider using isdigit or IsDigit? ***
|
|
// Assert(FALSE) (placeholder)
|
|
if (*p >= '0' && *p <= '9')
|
|
hrResult = ParseMsgStatusResponse(ppilfLine, p, pirParseResult);
|
|
else {
|
|
// It wasn't a msg status response, try status response
|
|
hrResult = ParseStatusResponse(p, pirParseResult);
|
|
|
|
// Check for error. The only error we ignore in this case is
|
|
// IXP_E_IMAP_UNRECOGNIZED_RESP, since this only means we
|
|
// should try to parse as a server/mailbox response
|
|
if (FAILED(hrResult) &&
|
|
IXP_E_IMAP_UNRECOGNIZED_RESP != hrResult)
|
|
break;
|
|
|
|
if (irNONE == *pirParseResult)
|
|
// It wasn't a status response, check if it's server/mailbox resp
|
|
hrResult = ParseSvrMboxResponse(ppilfLine, p, pirParseResult);
|
|
}
|
|
} // if(cSPACE == *p)
|
|
else
|
|
// Must be a garbled response line
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
break; // case cUNTAGGED_RESPONSE_PREFIX
|
|
|
|
default:
|
|
// Assume it's a tagged response
|
|
|
|
// Check if response line is big enough to hold one of our tags
|
|
if ((*ppilfLine)->dwLengthOfFragment <= NUM_TAG_CHARS) {
|
|
hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
|
|
break;
|
|
}
|
|
|
|
// Skip past tag and check for the space
|
|
p = lpszSvrResponseLine + NUM_TAG_CHARS;
|
|
if (cSPACE == *p) {
|
|
// Server response fits spec'd form, parse status response
|
|
*p = '\0'; // Null-terminate at the tag, so we can retrieve it
|
|
|
|
// Inform caller that this response was tagged, and return tag
|
|
*lpbTaggedResponse = TRUE;
|
|
StrCpyN(lpszTagFromSvr, lpszSvrResponseLine, TAG_BUFSIZE);
|
|
|
|
// Now process and return status response
|
|
hrResult = ParseStatusResponse(p + 1, pirParseResult);
|
|
}
|
|
else
|
|
// Must be a garbled response line
|
|
hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
|
|
|
|
break; // case DEFAULT (assumed to be tagged)
|
|
} // switch (*lpszSvrResponseLine)
|
|
|
|
|
|
// If an error occurred, return contents of the last processed fragment
|
|
if (FAILED(hrResult))
|
|
StrCpyN(m_szLastResponseText, (*ppilfLine)->data.pszSource, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
return hrResult;
|
|
} // ParseSvrResponseLine
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseStatusResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses and acts on Status Responses (section 7.1 of
|
|
// RFC-1730) (ie, OK/NO/BAD/PREAUTH/BYE). Response codes (eg, ALERT,
|
|
// TRYCREATE) are dispatched to a helper function, ParseResponseCode, for
|
|
// processing. The human-readable text associated with the response is
|
|
// stored in the module variable, m_szLastResponseText.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszStatusResponseLine [in] - a pointer to the text which possibly
|
|
// represents a status response. The text should not include the first
|
|
// part of the line, which identifies the response as tagged ("0000 ")
|
|
// or untagged ("* ").
|
|
// IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
|
|
// if we recognized it. Otherwise does not write a value out. The caller
|
|
// must initialize this variable to irNONE before calling this function.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If this function identifies the
|
|
// response as a status response, it returns S_OK. If it cannot recognize
|
|
// the response, it returns IXP_E_IMAP_UNRECOGNIZED_RESP. If we recognize
|
|
// the response but not the response CODE, it returns
|
|
// IXP_S_IMAP_UNRECOGNIZED_RESP (success code because we don't want
|
|
// to ever send an error to user based on unrecognized response code).
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseStatusResponse (LPSTR lpszStatusResponseLine,
|
|
IMAP_RESPONSE_ID *pirParseResult)
|
|
{
|
|
HRESULT hrResult;
|
|
LPSTR lpszResponseText;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszStatusResponseLine);
|
|
Assert(NULL != pirParseResult);
|
|
Assert(irNONE == *pirParseResult);
|
|
|
|
hrResult = S_OK;
|
|
|
|
// We can distinguish between all status responses by looking at second char
|
|
// First, determine that string is at least 1 character long
|
|
if ('\0' == *lpszStatusResponseLine)
|
|
return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a status response that we understand
|
|
|
|
lpszResponseText = lpszStatusResponseLine;
|
|
switch (*(lpszStatusResponseLine+1)) {
|
|
int iResult;
|
|
|
|
case 'k':
|
|
case 'K': // Possibly the "OK" Status response
|
|
iResult = StrCmpNI(lpszStatusResponseLine, "OK ", 3);
|
|
if (0 == iResult) {
|
|
// Definitely an "OK" status response
|
|
*pirParseResult = irOK_RESPONSE;
|
|
lpszResponseText += 3;
|
|
}
|
|
break; // case 'K' for possible "OK"
|
|
|
|
case 'o':
|
|
case 'O': // Possibly the "NO" status response
|
|
iResult = StrCmpNI(lpszStatusResponseLine, "NO ", 3);
|
|
if (0 == iResult) {
|
|
// Definitely a "NO" response
|
|
*pirParseResult = irNO_RESPONSE;
|
|
lpszResponseText += 3;
|
|
}
|
|
break; // case 'O' for possible "NO"
|
|
|
|
case 'a':
|
|
case 'A': // Possibly the "BAD" status response
|
|
iResult = StrCmpNI(lpszStatusResponseLine, "BAD ", 4);
|
|
if (0 == iResult) {
|
|
// Definitely a "BAD" response
|
|
*pirParseResult = irBAD_RESPONSE;
|
|
lpszResponseText += 4;
|
|
}
|
|
break; // case 'A' for possible "BAD"
|
|
|
|
case 'r':
|
|
case 'R': // Possibly the "PREAUTH" status response
|
|
iResult = StrCmpNI(lpszStatusResponseLine, "PREAUTH ", 8);
|
|
if (0 == iResult) {
|
|
// Definitely a "PREAUTH" response:
|
|
// PREAUTH is issued only as a greeting - check for proper context
|
|
// If improper context, ignore PREAUTH response
|
|
if (ssConnecting == m_ssServerState) {
|
|
*pirParseResult = irPREAUTH_RESPONSE;
|
|
lpszResponseText += 8;
|
|
m_ssServerState = ssAuthenticated;
|
|
}
|
|
}
|
|
break; // case 'R' for possible "PREAUTH"
|
|
|
|
case 'y':
|
|
case 'Y': // Possibly the "BYE" status response
|
|
iResult = StrCmpNI(lpszStatusResponseLine, "BYE ", 4);
|
|
if (0 == iResult) {
|
|
// Definitely a "BYE" response:
|
|
// Set server state to not connected
|
|
*pirParseResult = irBYE_RESPONSE;
|
|
lpszResponseText += 4;
|
|
m_ssServerState = ssNotConnected;
|
|
}
|
|
break; // case 'Y' for possible "BYE"
|
|
} // switch (*(lpszStatusResponseLine+1))
|
|
|
|
// If we recognized the command, proceed to process the response code
|
|
if (SUCCEEDED(hrResult) && irNONE != *pirParseResult) {
|
|
// We recognized the command, so lpszResponseText points to resp_text
|
|
// as defined in RFC-1730. Look for optional response code
|
|
if ('[' == *lpszResponseText) {
|
|
HRESULT hrResponseCodeResult;
|
|
|
|
hrResponseCodeResult = ParseResponseCode(lpszResponseText + 1);
|
|
if (FAILED(hrResponseCodeResult))
|
|
hrResult = hrResponseCodeResult;
|
|
}
|
|
else
|
|
// No response code, record response text for future retrieval
|
|
StrCpyN(m_szLastResponseText, lpszResponseText, ARRAYSIZE(m_szLastResponseText));
|
|
}
|
|
|
|
// If we didn't recognize the command, translate hrResult
|
|
if (SUCCEEDED(hrResult) && irNONE == *pirParseResult)
|
|
hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
|
|
|
|
return hrResult;
|
|
|
|
} // ParseStatusResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseResponseCode
|
|
//
|
|
// Purpose:
|
|
// This function parses and acts on the response code which may be
|
|
// returned with a status response (eg, PERMANENTFLAGS or ALERT). It is
|
|
// called by ParseStatusResponse upon detection of a response code. This
|
|
// function saves the human-readable text of the response code to
|
|
// m_szLastResponseLine.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszResponseCode [in] - a pointer to the response code portion
|
|
// of a response line, omitting the opening bracket ("[").
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If we cannot recognize the
|
|
// response code, we return IXP_S_IMAP_UNRECOGNIZED_RESP.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseResponseCode(LPSTR lpszResponseCode)
|
|
{
|
|
HRESULT hrResult;
|
|
WORD wHashValue;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszResponseCode);
|
|
|
|
hrResult = IXP_S_IMAP_UNRECOGNIZED_RESP;
|
|
|
|
switch (*lpszResponseCode) {
|
|
int iResult;
|
|
|
|
case 'A':
|
|
case 'a': // Possibly the "ALERT" response code
|
|
iResult = StrCmpNI(lpszResponseCode, "ALERT] ", 7);
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Definitely the "ALERT" response code:
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = lpszResponseCode + 7;
|
|
irIMAPResponse.irtResponseType = irtSERVER_ALERT;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
hrResult = S_OK;
|
|
break;
|
|
}
|
|
|
|
// *** FALL THROUGH *** to default case
|
|
|
|
case 'P':
|
|
case 'p': // Possibly the "PARSE" or "PERMANENTFLAGS" response code
|
|
iResult = StrCmpNI(lpszResponseCode, "PERMANENTFLAGS ", 15);
|
|
if (0 == iResult) {
|
|
IMAP_MSGFLAGS PermaFlags;
|
|
LPSTR p;
|
|
DWORD dwNumBytesRead;
|
|
|
|
// Definitely the "PERMANENTFLAGS" response code:
|
|
// Parse flag list
|
|
p = lpszResponseCode + 15; // p now points to start of flag list
|
|
hrResult = ParseMsgFlagList(p, &PermaFlags, &dwNumBytesRead);
|
|
if (SUCCEEDED(hrResult)) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Record response text
|
|
p += dwNumBytesRead + 3; // p now points to response text
|
|
StrCpyN(m_szLastResponseText, p, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irPERMANENTFLAGS_RESPONSECODE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtPERMANENT_FLAGS;
|
|
irIMAPResponse.irdResponseData.imfImapMessageFlags = PermaFlags;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
}
|
|
break;
|
|
} // end of PERMANENTFLAGS response code
|
|
|
|
iResult = StrCmpNI(lpszResponseCode, "PARSE] ", 7);
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Definitely the "PARSE" response code:
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = lpszResponseCode + 7;
|
|
irIMAPResponse.irtResponseType = irtPARSE_ERROR;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
hrResult = S_OK;
|
|
break;
|
|
} // end of PARSE response code
|
|
|
|
// *** FALL THROUGH *** to default case
|
|
|
|
case 'R':
|
|
case 'r': // Possibly "READ-ONLY" or "READ-WRITE" response
|
|
iResult = StrCmpNI(lpszResponseCode, "READ-WRITE] ", 12);
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Definitely the "READ-WRITE" response code:
|
|
hrResult = S_OK;
|
|
|
|
// Record this for enforcement purposes
|
|
m_bCurrentMboxReadOnly = FALSE;
|
|
|
|
// Record response text for future reference
|
|
StrCpyN(m_szLastResponseText, lpszResponseCode + 12, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irREADWRITE_RESPONSECODE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtREADWRITE_STATUS;
|
|
irIMAPResponse.irdResponseData.bReadWrite = TRUE;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
|
|
break;
|
|
} // end of READ-WRITE response
|
|
|
|
iResult = StrCmpNI(lpszResponseCode, "READ-ONLY] ", 11);
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Definitely the "READ-ONLY" response code:
|
|
hrResult = S_OK;
|
|
|
|
// Record this for enforcement purposes
|
|
m_bCurrentMboxReadOnly = TRUE;
|
|
|
|
// Record response text for future reference
|
|
StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irREADONLY_RESPONSECODE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtREADWRITE_STATUS;
|
|
irIMAPResponse.irdResponseData.bReadWrite = FALSE;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
|
|
break;
|
|
} // end of READ-ONLY response
|
|
|
|
// *** FALL THROUGH *** to default case
|
|
|
|
case 'T':
|
|
case 't': // Possibly the "TRYCREATE" response
|
|
iResult = StrCmpNI(lpszResponseCode, "TRYCREATE] ", 11);
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Definitely the "TRYCREATE" response code:
|
|
hrResult = S_OK;
|
|
|
|
StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irTRYCREATE_RESPONSECODE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtTRYCREATE;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
break;
|
|
}
|
|
|
|
// *** FALL THROUGH *** to default case
|
|
|
|
case 'U':
|
|
case 'u': // Possibly the "UIDVALIDITY" or "UNSEEN" response codes
|
|
iResult = StrCmpNI(lpszResponseCode, "UIDVALIDITY ", 12);
|
|
if (0 == iResult) {
|
|
LPSTR p, lpszEndOfNumber;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
// Definitely the "UIDVALIDITY" response code:
|
|
hrResult = S_OK;
|
|
|
|
// Return value to our caller so they can determine sync issues
|
|
p = lpszResponseCode + 12; // p points to UID number
|
|
lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket
|
|
if (NULL == lpszEndOfNumber) {
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
break;
|
|
}
|
|
|
|
*lpszEndOfNumber = '\0'; // Null-terminate the number
|
|
AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?");
|
|
|
|
StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText));
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irUIDVALIDITY_RESPONSECODE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = m_szLastResponseText;
|
|
irIMAPResponse.irtResponseType = irtUIDVALIDITY;
|
|
irIMAPResponse.irdResponseData.dwUIDValidity = StrToUint(p);
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
break;
|
|
} // end of UIDVALIDITY response code
|
|
|
|
iResult = StrCmpNI(lpszResponseCode, "UNSEEN ", 7);
|
|
if (0 == iResult) {
|
|
LPSTR p, lpszEndOfNumber;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
MBOX_MSGCOUNT mcMsgCount;
|
|
|
|
// Definitely the "UNSEEN" response code:
|
|
hrResult = S_OK;
|
|
|
|
// Record the returned number for reference during new mail DL
|
|
p = lpszResponseCode + 7; // p now points to first unseen msg num
|
|
lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket
|
|
if (NULL == lpszEndOfNumber) {
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
break;
|
|
}
|
|
|
|
*lpszEndOfNumber = '\0'; // Null-terminate the number
|
|
|
|
// Store response code for notification after command completion
|
|
mcMsgCount = mcMsgCount_INIT;
|
|
mcMsgCount.dwUnseen = StrToUint(p);
|
|
mcMsgCount.bGotUnseenResponse = TRUE;
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant here
|
|
irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
|
|
irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?");
|
|
|
|
StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText));
|
|
break;
|
|
} // end of UNSEEN response code
|
|
|
|
// *** FALL THROUGH *** to default case
|
|
|
|
default:
|
|
StrCpyN(m_szLastResponseText, lpszResponseCode, ARRAYSIZE(m_szLastResponseText));
|
|
break; // Default case: response code not recognized
|
|
} // switch(*lpszResponseCode)
|
|
|
|
return hrResult;
|
|
|
|
} // ParseResponseCode
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseSvrMboxResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses and acts on Server and Mailbox Status Responses
|
|
// from the IMAP server (see section 7.2 of RFC-1730) (eg, CAPABILITY and
|
|
// SEARCH responses).
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the IMAP line
|
|
// fragment to parse. It is used to retrieve the literals sent with the
|
|
// response. This pointer is updated so that it always points to the last
|
|
// processed fragment. The caller need only free the last fragment. All
|
|
// other fragments will already be freed when this function returns.
|
|
// LPSTR lpszSvrMboxResponseLine [in] - a pointer to the svr/mbox response
|
|
// line, omitting the first part of the line which identifies the response
|
|
// as tagged ("0001 ") or untagged ("* ").
|
|
// IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
|
|
// if we recognized it. Otherwise does not write a value out. The caller
|
|
// must initialize this variable to irNONE before calling this function.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If the response is not recognized,
|
|
// this function returns IXP_E_IMAP_UNRECOGNIZED_RESP.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseSvrMboxResponse (IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR lpszSvrMboxResponseLine,
|
|
IMAP_RESPONSE_ID *pirParseResult)
|
|
{
|
|
LPSTR pszTok;
|
|
HRESULT hrResult;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != lpszSvrMboxResponseLine);
|
|
Assert(NULL != pirParseResult);
|
|
Assert(irNONE == *pirParseResult);
|
|
|
|
hrResult = S_OK;
|
|
|
|
// We can ID all svr/mbox status responses by looking at second char
|
|
// First, determine that the line is at least 1 character long
|
|
if ('\0' == *lpszSvrMboxResponseLine)
|
|
return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a svr/mbox response
|
|
|
|
switch (*(lpszSvrMboxResponseLine+1)) {
|
|
int iResult;
|
|
|
|
case 'a':
|
|
case 'A': // Possibly the "CAPABILITY" response
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "CAPABILITY ", 11);
|
|
if (0 == iResult) {
|
|
LPSTR p;
|
|
|
|
// Definitely a "CAPABILITY" response
|
|
*pirParseResult = irCAPABILITY_RESPONSE;
|
|
|
|
// Search for and record known capabilities, discard unknowns
|
|
p = lpszSvrMboxResponseLine + 11; // p points to first cap. token
|
|
|
|
pszTok = p;
|
|
p = StrTokEx(&pszTok, g_szSpace); // p now points to next token
|
|
while (NULL != p) {
|
|
parseCapability(p); // Record capabilities which we recognize
|
|
p = StrTokEx(&pszTok, g_szSpace); // Grab next capability token
|
|
}
|
|
} // if(0 == iResult)
|
|
break; // case 'A' for possible "CAPABILITY"
|
|
|
|
case 'i':
|
|
case 'I': // Possibly the "LIST" response:
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "LIST ", 5);
|
|
if (0 == iResult) {
|
|
// Definitely a "LIST" response
|
|
*pirParseResult = irLIST_RESPONSE;
|
|
hrResult = ParseListLsubResponse(ppilfLine,
|
|
lpszSvrMboxResponseLine + 5, irLIST_RESPONSE);
|
|
} // if (0 == iResult)
|
|
break; // case 'I' for possible "LIST"
|
|
|
|
case 's':
|
|
case 'S': // Possibly the "LSUB" response:
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "LSUB ", 5);
|
|
if (0 == iResult) {
|
|
// Definitely a "LSUB" response:
|
|
*pirParseResult = irLSUB_RESPONSE;
|
|
hrResult = ParseListLsubResponse(ppilfLine,
|
|
lpszSvrMboxResponseLine + 5, irLSUB_RESPONSE);
|
|
} // if (0 == iResult)
|
|
break; // case 'S' for possible "LSUB"
|
|
|
|
case 'e':
|
|
case 'E': // Possibly the "SEARCH" response:
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "SEARCH", 6);
|
|
if (0 == iResult) {
|
|
// Definitely a "SEARCH" response:
|
|
*pirParseResult = irSEARCH_RESPONSE;
|
|
|
|
// Response can be "* SEARCH" or "* SEARCH <nums>". Check for null case
|
|
if (cSPACE == *(lpszSvrMboxResponseLine + 6))
|
|
hrResult = ParseSearchResponse(lpszSvrMboxResponseLine + 7);
|
|
}
|
|
break; // case 'E' for possible "SEARCH"
|
|
|
|
case 'l':
|
|
case 'L': // Possibly the "FLAGS" response:
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "FLAGS ", 6);
|
|
if (0 == iResult) {
|
|
IMAP_MSGFLAGS FlagsResult;
|
|
DWORD dwThrowaway;
|
|
|
|
// Definitely a "FLAGS" response:
|
|
*pirParseResult = irFLAGS_RESPONSE;
|
|
|
|
// Parse flag list
|
|
hrResult = ParseMsgFlagList(lpszSvrMboxResponseLine + 6,
|
|
&FlagsResult, &dwThrowaway);
|
|
|
|
if (SUCCEEDED(hrResult)) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irFLAGS_RESPONSE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
irIMAPResponse.irtResponseType = irtAPPLICABLE_FLAGS;
|
|
irIMAPResponse.irdResponseData.imfImapMessageFlags = FlagsResult;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
}
|
|
} // if (0 == iResult)
|
|
break; // Case 'L' for possible "FLAGS" response
|
|
|
|
case 't':
|
|
case 'T': // Possibly the "STATUS" response:
|
|
iResult = StrCmpNI(lpszSvrMboxResponseLine, "STATUS ", 7);
|
|
if (0 == iResult) {
|
|
// Definitely a "STATUS" response
|
|
*pirParseResult = irSTATUS_RESPONSE;
|
|
hrResult = ParseMboxStatusResponse(ppilfLine,
|
|
lpszSvrMboxResponseLine + 7);
|
|
} // if (0 == iResult)
|
|
break; // Case 'T' for possible "STATUS" response
|
|
|
|
} // case(*(lpszSvrMboxResponseLine+1))
|
|
|
|
// Did we recognize the response? Return error if we didn't
|
|
if (irNONE == *pirParseResult && SUCCEEDED(hrResult))
|
|
hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
|
|
|
|
return hrResult;
|
|
|
|
} // ParseSvrMboxResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseMsgStatusResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses and acts on Message Status Responses from the IMAP
|
|
// server (see section 7.3 of RFC-1730) (eg, FETCH and EXISTS responses).
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the IMAP line
|
|
// fragment to parse. It is used to retrieve the literals sent with the
|
|
// response. This pointer is updated so that it always points to the last
|
|
// processed fragment. The caller need only free the last fragment. All
|
|
// other fragments will already be freed when this function returns.
|
|
// LPSTR lpszMsgResponseLine [in] - pointer to response line, starting at
|
|
// the number argument.
|
|
// IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
|
|
// if we recognized it. Otherwise does not write a value out. The caller
|
|
// must initialize this variable to irNONE before calling this function.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If the response is not recognized,
|
|
// this function returns IXP_E_IMAP_UNRECOGNIZED_RESP.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseMsgStatusResponse (IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR lpszMsgResponseLine,
|
|
IMAP_RESPONSE_ID *pirParseResult)
|
|
{
|
|
HRESULT hrResult;
|
|
WORD wHashValue;
|
|
DWORD dwNumberArg;
|
|
LPSTR p;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != lpszMsgResponseLine);
|
|
Assert(NULL != pirParseResult);
|
|
Assert(irNONE == *pirParseResult);
|
|
|
|
hrResult = S_OK;
|
|
|
|
// First, fetch the number argument
|
|
p = StrChr(lpszMsgResponseLine, cSPACE); // Find the end of the number
|
|
if (NULL == p)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
dwNumberArg = StrToUint(lpszMsgResponseLine);
|
|
p += 1; // p now points to start of message response identifier
|
|
|
|
switch (*p) {
|
|
int iResult;
|
|
|
|
case 'E':
|
|
case 'e': // Possibly the "EXISTS" or "EXPUNGE" response
|
|
iResult = lstrcmpi(p, "EXISTS");
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
MBOX_MSGCOUNT mcMsgCount;
|
|
|
|
// Definitely the "EXISTS" response:
|
|
*pirParseResult = irEXISTS_RESPONSE;
|
|
|
|
// Record mailbox size for notification at completion of command
|
|
mcMsgCount = mcMsgCount_INIT;
|
|
mcMsgCount.dwExists = dwNumberArg;
|
|
mcMsgCount.bGotExistsResponse = TRUE;
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant here
|
|
irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
|
|
irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
break;
|
|
}
|
|
|
|
iResult = lstrcmpi(p, "EXPUNGE");
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Definitely the "EXPUNGE" response: Inform caller via callback
|
|
*pirParseResult = irEXPUNGE_RESPONSE;
|
|
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
irIMAPResponse.irtResponseType = irtDELETED_MSG;
|
|
irIMAPResponse.irdResponseData.dwDeletedMsgSeqNum = dwNumberArg;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
break;
|
|
}
|
|
|
|
break; // Case 'E' or 'e' for possible "EXISTS" or "EXPUNGE" response
|
|
|
|
|
|
case 'R':
|
|
case 'r': // Possibly the "RECENT" response
|
|
iResult = lstrcmpi(p, "RECENT");
|
|
if (0 == iResult) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
MBOX_MSGCOUNT mcMsgCount;
|
|
|
|
// Definitely the "RECENT" response:
|
|
*pirParseResult = irRECENT_RESPONSE;
|
|
|
|
// Record number for future reference
|
|
mcMsgCount = mcMsgCount_INIT;
|
|
mcMsgCount.dwRecent = dwNumberArg;
|
|
mcMsgCount.bGotRecentResponse = TRUE;
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant here
|
|
irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
|
|
irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
break; // Case 'R' or 'r' for possible "RECENT" response
|
|
|
|
|
|
case 'F':
|
|
case 'f': // Possibly the "FETCH" response
|
|
iResult = StrCmpNI(p, "FETCH ", 6);
|
|
if (0 == iResult) {
|
|
// Definitely the "FETCH" response
|
|
*pirParseResult = irFETCH_RESPONSE;
|
|
|
|
p += 6;
|
|
hrResult = ParseFetchResponse(ppilfLine, dwNumberArg, p);
|
|
} // if (0 == iResult)
|
|
break; // Case 'F' or 'f' for possible "FETCH" response
|
|
} // switch(*p)
|
|
|
|
// Did we recognize the response? Return error if we didn't
|
|
if (irNONE == *pirParseResult && SUCCEEDED(hrResult))
|
|
hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
|
|
|
|
return hrResult;
|
|
|
|
} // ParseMsgStatusResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseListLsubResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses LIST and LSUB responses and invokes the
|
|
// ListLsubResponseNotification() callback to inform the user.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
|
|
// IMAP response fragment. This is used to retrieve the next fragment
|
|
// in the chain (literal or line) since literals may be sent with LIST
|
|
// responses. This pointer is always updated to point to the fragment
|
|
// currently in use, so that the caller may free the last one himself.
|
|
// LPSTR lpszListResponse [in] - actually can be LIST or LSUB, but I don't
|
|
// want to have to type "ListLsub" all the time. This points into the
|
|
// middle of the LIST/LSUB response, where the mailbox_list begins (see
|
|
// RFC1730, Formal Syntax). In other words, the caller should skip past
|
|
// the initial "* LIST " or "* LSUB ", and so this ptr should point to
|
|
// a "(".
|
|
// IMAP_RESPONSE_ID irListLsubID [in] - either irLIST_RESPONSE or
|
|
// irLSUB_RESPONSE. This information is required so that we can retrieve
|
|
// the transaction ID associated with the response.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseListLsubResponse(IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR lpszListResponse,
|
|
IMAP_RESPONSE_ID irListLsubID)
|
|
{
|
|
LPSTR p, lpszClosingParenthesis, pszTok;
|
|
HRESULT hrResult = S_OK;
|
|
HRESULT hrTranslateResult = E_FAIL;
|
|
IMAP_MBOXFLAGS MboxFlags;
|
|
char cHierarchyChar;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
IMAP_LISTLSUB_RESPONSE *pillrd;
|
|
LPSTR pszDecodedMboxName = NULL;
|
|
LPSTR pszMailboxName = NULL;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != lpszListResponse);
|
|
Assert(irLIST_RESPONSE == irListLsubID ||
|
|
irLSUB_RESPONSE == irListLsubID);
|
|
|
|
// We received an untagged LIST/LSUB response
|
|
// lpszListResponse = <flag list> <hierarchy char> <mailbox name>
|
|
if ('(' != *lpszListResponse)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR; // We expect an opening parenthesis
|
|
|
|
p = lpszListResponse + 1; // p now points to start of first flag token
|
|
|
|
// Find position of closing parenthesis. I don't like the
|
|
// lack of efficiency, but I can fix this later. Assert(FALSE) (placeholder)
|
|
lpszClosingParenthesis = StrChr(p, ')');
|
|
if (NULL == lpszClosingParenthesis)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR; // We expect a closing parenthesis
|
|
|
|
// Now process each mailbox flag returned by LIST/LSUB
|
|
*lpszClosingParenthesis = '\0'; // Null-terminate flag list
|
|
MboxFlags = IMAP_MBOX_NOFLAGS;
|
|
pszTok = p;
|
|
p = StrTokEx(&pszTok, g_szSpace); // Null-terminate first flag token
|
|
while (NULL != p) {
|
|
MboxFlags |= ParseMboxFlag(p);
|
|
p = StrTokEx(&pszTok, g_szSpace); // Grab next flag token
|
|
}
|
|
|
|
// Next, grab the hierarchy character, and advance p
|
|
// Server either sends (1) "<quoted char>" or (2) NIL
|
|
p = lpszClosingParenthesis + 1; // p now points past flag list
|
|
if (cSPACE == *p) {
|
|
LPSTR pszHC = NULL;
|
|
DWORD dwLengthOfHC;
|
|
|
|
p += 1; // p now points to start of hierarchy char spec
|
|
|
|
hrResult = NStringToString(ppilfLine, &pszHC, &dwLengthOfHC, &p);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
if (hrIMAP_S_NIL_NSTRING == hrResult)
|
|
cHierarchyChar = '\0'; // Got a "NIL" for hierarchy char
|
|
else if (hrIMAP_S_QUOTED == hrResult) {
|
|
if (1 != dwLengthOfHC)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR; // We should only exactly ONE char back!
|
|
else
|
|
cHierarchyChar = pszHC[0];
|
|
}
|
|
else {
|
|
// It's a literal, or something else unexpected
|
|
MemFree(pszHC);
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
}
|
|
MemFree(pszHC);
|
|
|
|
// p now points past the closing quote (thanks to NStringToString)
|
|
}
|
|
else
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
|
|
if (cSPACE != *p)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
|
|
// Grab the mailbox name - assume size of lpszListResponse is
|
|
// whatever p has already uncovered. We expect nothing past
|
|
// this point, so we should be safe.
|
|
p += 1;
|
|
hrResult = AStringToString(ppilfLine, &pszMailboxName, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
// Convert the mailbox name from UTF7 to MultiByte and remember the result
|
|
hrTranslateResult = _ModifiedUTF7ToMultiByte(pszMailboxName, &pszDecodedMboxName);
|
|
if (FAILED(hrTranslateResult)) {
|
|
hrResult = hrTranslateResult;
|
|
goto error;
|
|
}
|
|
|
|
// Make sure the command line is finished (debug only)
|
|
Assert('\0' == *p);
|
|
|
|
// Notify the caller of our findings
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irListLsubID);
|
|
irIMAPResponse.hrResult = hrTranslateResult; // Could be IXP_S_IMAP_VERBATIM_MBOX
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
irIMAPResponse.irtResponseType = irtMAILBOX_LISTING;
|
|
|
|
pillrd = &irIMAPResponse.irdResponseData.illrdMailboxListing;
|
|
pillrd->pszMailboxName = pszDecodedMboxName;
|
|
pillrd->imfMboxFlags = MboxFlags;
|
|
pillrd->cHierarchyChar = cHierarchyChar;
|
|
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
|
|
error:
|
|
if (NULL != pszDecodedMboxName)
|
|
MemFree(pszDecodedMboxName);
|
|
|
|
if (NULL != pszMailboxName)
|
|
MemFree(pszMailboxName);
|
|
|
|
return hrResult;
|
|
} // ParseListLsubResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseMboxFlag
|
|
//
|
|
// Purpose:
|
|
// Given a mailbox_list flag (see RFC1730, Formal Syntax), this function
|
|
// returns the IMAP_MBOX_* value which corresponds to that mailbox flag.
|
|
// For instance, given the string, "\Noinferiors", this function returns
|
|
// IMAP_MBOX_NOINFERIORS.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszFlagToken [in] - a null-terminated string representing a
|
|
// mailbox_list flag.
|
|
//
|
|
// Returns:
|
|
// IMAP_MBOXFLAGS value. If flag is unrecognized, IMAP_MBOX_NOFLAGS is
|
|
// returned.
|
|
//***************************************************************************
|
|
IMAP_MBOXFLAGS CImap4Agent::ParseMboxFlag(LPSTR lpszFlagToken)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszFlagToken);
|
|
|
|
// We can identify the mailbox flags we know about by looking at the
|
|
// fourth character of the flag name. $REVIEW: you don't have to check
|
|
// the initial backslash, during lstrcmpi call in switch statement
|
|
|
|
// First, check that there are at least three characters
|
|
if ('\\' != *lpszFlagToken ||
|
|
'\0' == *(lpszFlagToken + 1) ||
|
|
'\0' == *(lpszFlagToken + 2))
|
|
return IMAP_MBOX_NOFLAGS;
|
|
|
|
switch (*(lpszFlagToken + 3)) {
|
|
int iResult;
|
|
|
|
case 'R':
|
|
case 'r': // Possible "\Marked" flag
|
|
iResult = lstrcmpi(lpszFlagToken, "\\Marked");
|
|
if (0 == iResult)
|
|
return IMAP_MBOX_MARKED; // Definitely the \Marked flag
|
|
|
|
break; // case 'r': // Possible "\Marked" flag
|
|
|
|
case 'I':
|
|
case 'i': // Possible "\Noinferiors" flag
|
|
iResult = lstrcmpi(lpszFlagToken, "\\Noinferiors");
|
|
if (0 == iResult)
|
|
return IMAP_MBOX_NOINFERIORS; // Definitely the \Noinferiors flag
|
|
|
|
break; // case 'i': // Possible "\Noinferiors" flag
|
|
|
|
case 'S':
|
|
case 's': // Possible "\Noselect" flag
|
|
iResult = lstrcmpi(lpszFlagToken, "\\Noselect");
|
|
if (0 == iResult)
|
|
return IMAP_MBOX_NOSELECT; // Definitely the \Noselect flag
|
|
|
|
break; // case 's': // Possible "\Noselect" flag
|
|
|
|
case 'M':
|
|
case 'm': // Possible "\Unmarked" flag
|
|
iResult = lstrcmpi(lpszFlagToken, "\\Unmarked");
|
|
if (0 == iResult)
|
|
return IMAP_MBOX_UNMARKED;
|
|
|
|
break; // case 'm': // Possible "\Unmarked" flag
|
|
} // switch (*(lpszFlagToken + 3))
|
|
|
|
return IMAP_MBOX_NOFLAGS;
|
|
} // ParseMboxFlag
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseFetchResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses FETCH responses and calls the
|
|
// UpdateMsgNotification() callback to inform the user.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
|
|
// IMAP response fragment. This is used to retrieve the next fragment
|
|
// in the chain (literal or line) since literals may be sent with FETCH
|
|
// responses. This pointer is always updated to point to the fragment
|
|
// currently in use, so that the caller may free the last one himself.
|
|
// DWORD dwMsgSeqNum [in] - message sequence number of this fetch resp.
|
|
// LPSTR lpszFetchResp [in] - a pointer to the portion of the fetch
|
|
// response after "<num> FETCH " (the msg_att portion of a message_data
|
|
// item. See RFC1730 formal syntax).
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseFetchResponse (IMAP_LINE_FRAGMENT **ppilfLine,
|
|
DWORD dwMsgSeqNum, LPSTR lpszFetchResp)
|
|
{
|
|
LPSTR p;
|
|
FETCH_CMD_RESULTS_EX fetchResults;
|
|
FETCH_CMD_RESULTS fcrOldFetchStruct;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(0 != dwMsgSeqNum);
|
|
Assert(NULL != lpszFetchResp);
|
|
|
|
// Initialize variables
|
|
ZeroMemory(&fetchResults, sizeof(fetchResults));
|
|
|
|
p = lpszFetchResp;
|
|
if ('(' != *p) {
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR; // We expect opening parenthesis
|
|
goto exit;
|
|
}
|
|
|
|
|
|
// Parse each FETCH response tag (eg, RFC822, FLAGS, etc.)
|
|
hrResult = S_OK;
|
|
do {
|
|
// We'll identify FETCH tags based on the first character of tag
|
|
p += 1; // Advance p to first char
|
|
switch (*p) {
|
|
int iResult;
|
|
|
|
case 'b':
|
|
case 'B':
|
|
case 'r':
|
|
case 'R':
|
|
iResult = StrCmpNI(p, "RFC822.SIZE ", 12);
|
|
if (0 == iResult) {
|
|
// Definitely the RFC822.SIZE tag:
|
|
// Read the nstring into a stream
|
|
p += 12; // Advance p to point to number
|
|
|
|
fetchResults.bRFC822Size = TRUE;
|
|
fetchResults.dwRFC822Size = StrToUint(p);
|
|
|
|
// Advance p to point past number
|
|
while ('0' <= *p && '9' >= *p)
|
|
p += 1;
|
|
|
|
break; // case 'r' or 'R': Possible RFC822.SIZE tag
|
|
} // if (0 == iResult) for RFC822.HEADER
|
|
|
|
if (0 == StrCmpNI(p, "RFC822", 6) || 0 == StrCmpNI(p, "BODY[", 5)) {
|
|
LPSTR pszBodyTag;
|
|
LPSTR pszBody;
|
|
DWORD dwLengthOfBody;
|
|
IMAP_LINE_FRAGMENT *pilfBodyTag = NULL; // Line fragment containing the body tag
|
|
|
|
// Find the body tag. We null-terminate all body tags after first space
|
|
pszBodyTag = p;
|
|
p = StrChr(p + 6, cSPACE);
|
|
if (NULL == p) {
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
goto exit;
|
|
}
|
|
|
|
*p = '\0'; // Null-terminate the body tag
|
|
p += 1; // Advance p to point to nstring
|
|
|
|
// Check if this is BODY[HEADER.FIELDS: this is the only tag that can
|
|
// include spaces and literals. We must skip past all of these.
|
|
if (0 == lstrcmpi("BODY[HEADER.FIELDS", pszBodyTag)) {
|
|
|
|
// Advance p until we find a ']'
|
|
while ('\0' != *p && ']' != *p) {
|
|
p += 1;
|
|
|
|
// Check for end of this string buffer
|
|
if ('\0' == *p) {
|
|
if (NULL == pilfBodyTag)
|
|
pilfBodyTag = *ppilfLine; // Retain for future reference
|
|
|
|
// Advance to next fragment, discarding any literals that we find
|
|
do {
|
|
if (NULL == (*ppilfLine)->pilfNextFragment) {
|
|
// No more runway! Couldn't find ']'. Free all data and bail
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
while (NULL != pilfBodyTag && pilfBodyTag != *ppilfLine) {
|
|
IMAP_LINE_FRAGMENT *pilfDead;
|
|
|
|
pilfDead = pilfBodyTag;
|
|
pilfBodyTag = pilfBodyTag->pilfNextFragment;
|
|
FreeFragment(&pilfDead);
|
|
}
|
|
goto exit;
|
|
}
|
|
else
|
|
*ppilfLine = (*ppilfLine)->pilfNextFragment;
|
|
} while (iltLINE != (*ppilfLine)->iltFragmentType);
|
|
|
|
p = (*ppilfLine)->data.pszSource;
|
|
}
|
|
}
|
|
|
|
// Terminate HEADER.FIELDS chain but keep it around because we may need pszBodyTag
|
|
if (NULL != pilfBodyTag && NULL != (*ppilfLine)->pilfPrevFragment)
|
|
(*ppilfLine)->pilfPrevFragment->pilfNextFragment = NULL;
|
|
|
|
Assert(']' == *p);
|
|
Assert(cSPACE == *(p+1));
|
|
p += 2; // This should point us to the body nstring
|
|
}
|
|
|
|
// Read the nstring into a string
|
|
hrResult = NStringToString(ppilfLine, &pszBody, &dwLengthOfBody, &p);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// If literal, it's already been handled. If NIL or string, report it to user
|
|
if (hrIMAP_S_QUOTED == hrResult || hrIMAP_S_NIL_NSTRING == hrResult) {
|
|
PrepareForFetchBody(dwMsgSeqNum, dwLengthOfBody, pszBodyTag);
|
|
m_dwLiteralInProgressBytesLeft = 0; // Override this
|
|
DispatchFetchBodyPart(pszBody, dwLengthOfBody, fDONT_FREE_BODY_TAG);
|
|
Assert(irsIDLE == m_irsState);
|
|
}
|
|
|
|
// Free any chains associated with HEADER.FIELDS
|
|
while (NULL != pilfBodyTag) {
|
|
IMAP_LINE_FRAGMENT *pilfDead;
|
|
|
|
pilfDead = pilfBodyTag;
|
|
pilfBodyTag = pilfBodyTag->pilfNextFragment;
|
|
FreeFragment(&pilfDead);
|
|
}
|
|
|
|
MemFree(pszBody);
|
|
break;
|
|
} // if FETCH body tag like RFC822* or BODY[*
|
|
|
|
// If not recognized, flow through (long way) to default case
|
|
|
|
case 'u':
|
|
case 'U':
|
|
iResult = StrCmpNI(p, "UID ", 4);
|
|
if (0 == iResult) {
|
|
LPSTR lpszUID;
|
|
|
|
// Definitely the UID tag
|
|
// First, find the end of the number (and verify it)
|
|
p += 4; // p now points to start of UID
|
|
lpszUID = p;
|
|
while ('\0' != *p && *p >= '0' && *p <= '9') // $REVIEW: isDigit?
|
|
p += 1;
|
|
|
|
// OK, we found end of number, and verified number is all digits
|
|
fetchResults.bUID = TRUE;
|
|
fetchResults.dwUID = StrToUint(lpszUID);
|
|
|
|
break; // case 'u' or 'U': Possible UID tag
|
|
} // if (0 == iResult)
|
|
|
|
// If not recognized, flow through (long way) to default case
|
|
|
|
case 'f':
|
|
case 'F':
|
|
iResult = StrCmpNI(p, "FLAGS ", 6);
|
|
if (0 == iResult) {
|
|
DWORD dwNumBytesRead;
|
|
|
|
// Definitely a FLAGS response: Parse the list
|
|
p += 6;
|
|
hrResult = ParseMsgFlagList(p, &fetchResults.mfMsgFlags,
|
|
&dwNumBytesRead);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
fetchResults.bMsgFlags = TRUE;
|
|
p += dwNumBytesRead + 1; // Advance p past end of flag list
|
|
|
|
break; // case 'f' or 'F': Possible FLAGS tag
|
|
} // if (0 == iResult)
|
|
|
|
// If not recognized, flow through to default case
|
|
|
|
case 'i':
|
|
case 'I':
|
|
iResult = StrCmpNI(p, "INTERNALDATE ", 13);
|
|
if (0 == iResult) {
|
|
LPSTR lpszEndOfDate;
|
|
|
|
// Definitely an INTERNALDATE response: convert to FILETIME
|
|
p += 13;
|
|
if ('\"' == *p)
|
|
p += 1; // Advance past the opening double-quote
|
|
else {
|
|
AssertSz(FALSE, "Server error: date_time starts without double-quote!");
|
|
}
|
|
|
|
lpszEndOfDate = StrChr(p, '\"'); // Find closing double-quote
|
|
if (NULL == lpszEndOfDate) {
|
|
AssertSz(FALSE, "Server error: date_time ends without double-quote!");
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR; // Can't continue, don't know where to go from
|
|
goto exit;
|
|
}
|
|
|
|
// Null-terminate end of date, for MimeOleInetDateToFileTime's sake
|
|
*lpszEndOfDate = '\0';
|
|
|
|
hrResult = MimeOleInetDateToFileTime(p, &fetchResults.ftInternalDate);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
p = lpszEndOfDate + 1;
|
|
fetchResults.bInternalDate = TRUE;
|
|
break; // case 'i' or 'I': Possible INTERNALDATE tag
|
|
} // (0 == iResult)
|
|
|
|
// If not recognized, flow through to default case
|
|
|
|
case 'e':
|
|
case 'E':
|
|
iResult = StrCmpNI(p, "ENVELOPE ", 9);
|
|
if (0 == iResult) {
|
|
// Definitely an envelope: parse each field!
|
|
p += 9;
|
|
hrResult = ParseEnvelope(&fetchResults, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
fetchResults.bEnvelope = TRUE;
|
|
break;
|
|
}
|
|
|
|
// If not recognized, flow through to default case
|
|
|
|
default:
|
|
// Unrecognized FETCH tag!
|
|
// $REVIEW: We should skip past the data based on common-sense
|
|
// rules. For now, just flip out. Be sure that above rules flow
|
|
// through to here if unrecognized cmd
|
|
Assert(FALSE);
|
|
goto exit;
|
|
break; // default case
|
|
} // switch (*lpszFetchResp)
|
|
|
|
// If *p is a space, we have another FETCH tag coming
|
|
} while (cSPACE == *p);
|
|
|
|
// Check if we ended on a closing parenthesis (as we always should)
|
|
if (')' != *p) {
|
|
hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
|
|
goto exit;
|
|
}
|
|
|
|
// Check that there's no stuff afterwards (debug only - retail ignores)
|
|
Assert('\0' == *(p+1));
|
|
|
|
exit:
|
|
// Finished parsing the FETCH response. Call the UPDATE callback
|
|
fetchResults.dwMsgSeqNum = dwMsgSeqNum;
|
|
// Persist the cookies from body part in progress
|
|
fetchResults.lpFetchCookie1 = m_fbpFetchBodyPartInProgress.lpFetchCookie1;
|
|
fetchResults.lpFetchCookie2 = m_fbpFetchBodyPartInProgress.lpFetchCookie2;
|
|
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = hrResult;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
|
|
if (IMAP_FETCHEX_ENABLE & m_dwFetchFlags)
|
|
{
|
|
irIMAPResponse.irtResponseType = irtUPDATE_MSG_EX;
|
|
irIMAPResponse.irdResponseData.pFetchResultsEx = &fetchResults;
|
|
}
|
|
else
|
|
{
|
|
DowngradeFetchResponse(&fcrOldFetchStruct, &fetchResults);
|
|
|
|
irIMAPResponse.irtResponseType = irtUPDATE_MSG;
|
|
irIMAPResponse.irdResponseData.pFetchResults = &fcrOldFetchStruct;
|
|
}
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT;
|
|
FreeFetchResponse(&fetchResults);
|
|
return hrResult;
|
|
} // ParseFetchResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseSearchResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses SEARCH responses and calls the
|
|
// SearchResponseNotification() callback to inform the user.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszFetchResp [in] - a pointer to the data of the search response.
|
|
// This means that the "* SEARCH" portion should be omitted.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseSearchResponse(LPSTR lpszSearchResponse)
|
|
{
|
|
LPSTR p, pszTok;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
IIMAPCallback *pCBHandler;
|
|
CRangeList *pSearchResults;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszSearchResponse);
|
|
|
|
// First, check for the situation where there are 0 responses
|
|
p = lpszSearchResponse;
|
|
while ('\0' != *p && ('0' > *p || '9' < *p))
|
|
p += 1; // Keep going until we hit a digit
|
|
|
|
if ('\0' == *p)
|
|
return S_OK;
|
|
|
|
// Create CRangeList object
|
|
pSearchResults = new CRangeList;
|
|
if (NULL == pSearchResults)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// Parse search responses
|
|
pszTok = lpszSearchResponse;
|
|
p = StrTokEx(&pszTok, g_szSpace);
|
|
while (NULL != p) {
|
|
DWORD dw;
|
|
|
|
dw = StrToUint(p);
|
|
if (0 != dw) {
|
|
HRESULT hrResult;
|
|
|
|
hrResult = pSearchResults->AddSingleValue(dw);
|
|
Assert(SUCCEEDED(hrResult));
|
|
}
|
|
else {
|
|
// Discard unusable results
|
|
AssertSz(FALSE, "Hmm, this server is into kinky search responses.");
|
|
}
|
|
|
|
p = StrTokEx(&pszTok, g_szSpace); // p now points to next number. $REVIEW: Use Opie's fstrtok!
|
|
}
|
|
|
|
// Notify user of search response.
|
|
GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
|
|
&pCBHandler, irSEARCH_RESPONSE);
|
|
irIMAPResponse.hrResult = S_OK;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
irIMAPResponse.irtResponseType = irtSEARCH;
|
|
irIMAPResponse.irdResponseData.prlSearchResults = (IRangeList *) pSearchResults;
|
|
OnIMAPResponse(pCBHandler, &irIMAPResponse);
|
|
|
|
pSearchResults->Release();
|
|
return S_OK;
|
|
} // ParseSearchResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseMboxStatusResponse
|
|
//
|
|
// Purpose:
|
|
// This function parses an untagged STATUS response and calls the default
|
|
// CB handler with an irtMAILBOX_STATUS callback.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
|
|
// IMAP response fragment. This is used to retrieve the next fragment
|
|
// in the chain (literal or line) since literals may be sent with STATUS
|
|
// responses. This pointer is always updated to point to the fragment
|
|
// currently in use, so that the caller may free the last one himself.
|
|
// LPSTR pszStatusResponse [in] - a pointer to the STATUS response, after
|
|
// the "<tag> STATUS " portion (should point to the mailbox parameter).
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseMboxStatusResponse(IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR pszStatusResponse)
|
|
{
|
|
LPSTR p, pszDecodedMboxName;
|
|
LPSTR pszMailbox;
|
|
HRESULT hrTranslateResult = E_FAIL;
|
|
HRESULT hrResult;
|
|
IMAP_STATUS_RESPONSE isrResult;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Initialize variables
|
|
p = pszStatusResponse;
|
|
ZeroMemory(&isrResult, sizeof(isrResult));
|
|
pszDecodedMboxName = NULL;
|
|
pszMailbox = NULL;
|
|
|
|
// Get the name of the mailbox
|
|
hrResult = AStringToString(ppilfLine, &pszMailbox, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Convert the mailbox name from UTF7 to MultiByte and remember the result
|
|
hrTranslateResult = _ModifiedUTF7ToMultiByte(pszMailbox, &pszDecodedMboxName);
|
|
if (FAILED(hrTranslateResult)) {
|
|
hrResult = hrTranslateResult;
|
|
goto exit;
|
|
}
|
|
|
|
// Advance to first status tag
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
Assert('(' == *p);
|
|
|
|
// Loop through all status attributes
|
|
while ('\0' != *p && ')' != *p) {
|
|
LPSTR pszTag, pszTagValue;
|
|
DWORD dwTagValue;
|
|
|
|
// Get pointers to tag and tag value
|
|
Assert('(' == *p || cSPACE == *p);
|
|
p += 1;
|
|
pszTag = p;
|
|
while ('\0' != *p && cSPACE != *p && ')' != *p)
|
|
p += 1;
|
|
|
|
Assert(cSPACE == *p); // We expect space, then tag value
|
|
if (cSPACE == *p) {
|
|
p += 1;
|
|
Assert(*p >= '0' && *p <= '9');
|
|
pszTagValue = p;
|
|
dwTagValue = StrToUint(p);
|
|
}
|
|
|
|
// Advance us past number to next tag in prep for next loop iteration
|
|
while ('\0' != *p && cSPACE != *p && ')' != *p)
|
|
p += 1;
|
|
|
|
switch (*pszTag) {
|
|
int iResult;
|
|
|
|
case 'm':
|
|
case 'M': // Possibly the "MESSAGES" attribute
|
|
iResult = StrCmpNI(pszTag, "MESSAGES ", 9);
|
|
if (0 == iResult) {
|
|
// Definitely the "MESSAGES" tag
|
|
isrResult.fMessages = TRUE;
|
|
isrResult.dwMessages = dwTagValue;
|
|
} // if (0 == iResult)
|
|
break; // case 'M' for possible "MESSAGES"
|
|
|
|
case 'r':
|
|
case 'R': // Possibly the "RECENT" attribute
|
|
iResult = StrCmpNI(pszTag, "RECENT ", 7);
|
|
if (0 == iResult) {
|
|
// Definitely the "RECENT" tag
|
|
isrResult.fRecent = TRUE;
|
|
isrResult.dwRecent = dwTagValue;
|
|
} // if (0 == iResult)
|
|
break; // case 'R' for possible "RECENT"
|
|
|
|
case 'u':
|
|
case 'U': // Possibly UIDNEXT, UIDVALIDITY or UNSEEN
|
|
// Check for the 3 possible tags in order of expected popularity
|
|
iResult = StrCmpNI(pszTag, "UNSEEN ", 7);
|
|
if (0 == iResult) {
|
|
// Definitely the "UNSEEN" tag
|
|
isrResult.fUnseen = TRUE;
|
|
isrResult.dwUnseen = dwTagValue;
|
|
} // if (0 == iResult)
|
|
|
|
iResult = StrCmpNI(pszTag, "UIDVALIDITY ", 12);
|
|
if (0 == iResult) {
|
|
// Definitely the "UIDVALIDITY" tag
|
|
isrResult.fUIDValidity = TRUE;
|
|
isrResult.dwUIDValidity = dwTagValue;
|
|
} // if (0 == iResult)
|
|
|
|
iResult = StrCmpNI(pszTag, "UIDNEXT ", 8);
|
|
if (0 == iResult) {
|
|
// Definitely the "UIDNEXT" tag
|
|
isrResult.fUIDNext = TRUE;
|
|
isrResult.dwUIDNext = dwTagValue;
|
|
} // if (0 == iResult)
|
|
break; // case 'U' for possible UIDNEXT, UIDVALIDITY or UNSEEN
|
|
} // switch (*p)
|
|
} // while ('\0' != *p)
|
|
Assert(')' == *p);
|
|
|
|
// Call the callback with our new-found information
|
|
isrResult.pszMailboxName = pszDecodedMboxName;
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = hrTranslateResult; // Could be IXP_S_IMAP_VERBATIM_MBOX
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant here
|
|
irIMAPResponse.irtResponseType = irtMAILBOX_STATUS;
|
|
irIMAPResponse.irdResponseData.pisrStatusResponse = &isrResult;
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
|
|
exit:
|
|
if (NULL != pszDecodedMboxName)
|
|
MemFree(pszDecodedMboxName);
|
|
|
|
if (NULL != pszMailbox)
|
|
MemFree(pszMailbox);
|
|
|
|
return hrResult;
|
|
} // ParseMboxStatusResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseEnvelope
|
|
//
|
|
// Purpose:
|
|
// This function parses the ENVELOPE tag returned via a FETCH response.
|
|
//
|
|
// Arguments:
|
|
// FETCH_CMD_RESULTS_EX *pEnvResults [out] - the results of parsing the
|
|
// ENVELOPE tag are outputted to this structure. It is the caller's
|
|
// responsibility to call FreeFetchResponse when finished with the data.
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current IMAP
|
|
// response fragment. This is advanced to the next fragment in the chain
|
|
// as necessary (due to literals). On function exit, this will point
|
|
// to the new current response fragment so the caller may continue parsing
|
|
// as usual.
|
|
// LPSTR *ppCurrent [in/out] - a pointer to the first '(' after the ENVELOPE
|
|
// tag. On function exit, this pointer is updated to point past the ')'
|
|
// after the ENVELOPE tag so the caller may continue parsing as usual.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseEnvelope(FETCH_CMD_RESULTS_EX *pEnvResults,
|
|
IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR *ppCurrent)
|
|
{
|
|
HRESULT hrResult;
|
|
LPSTR p;
|
|
LPSTR pszTemp;
|
|
|
|
TraceCall("CImap4Agent::ParseEnvelope");
|
|
|
|
p = *ppCurrent;
|
|
if ('(' != *p)
|
|
{
|
|
hrResult = TraceResult(IXP_E_IMAP_SVR_SYNTAXERR);
|
|
goto exit;
|
|
}
|
|
|
|
// (1) Parse the envelope date (ignore error)
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &pszTemp, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
hrResult = MimeOleInetDateToFileTime(pszTemp, &pEnvResults->ftENVDate);
|
|
MemFree(pszTemp);
|
|
TraceError(hrResult); // Record but otherwise ignore error
|
|
|
|
// (2) Get the "Subject" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVSubject, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (3) Get the "From" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVFrom, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (4) Get the "Sender" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVSender, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (5) Get the "Reply-To" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVReplyTo, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (6) Get the "To" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVTo, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (7) Get the "Cc" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVCc, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (8) Get the "Bcc" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = ParseIMAPAddresses(&pEnvResults->piaENVBcc, ppilfLine, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (9) Get the "InReplyTo" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVInReplyTo, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (10) Get the "MessageID" field
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVMessageID, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// Read in closing parenthesis
|
|
Assert(')' == *p);
|
|
p += 1;
|
|
|
|
exit:
|
|
*ppCurrent = p;
|
|
return hrResult;
|
|
} // ParseEnvelope
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseIMAPAddresses
|
|
//
|
|
// Purpose:
|
|
// This function parses a LIST of "address" constructs as defined in RFC2060
|
|
// formal syntax. There is no formal syntax token for this LIST, but an example
|
|
// can be found in the "env_from" token in RFC2060's formal syntax. This
|
|
// function would be called to parse "env_from".
|
|
//
|
|
// Arguments:
|
|
// IMAPADDR **ppiaResults [out] - a pointer to a chain of IMAPADDR structures
|
|
// is returned here.
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current IMAP
|
|
// response fragment. This is advanced to the next fragment in the chain
|
|
// as necessary (due to literals). On function exit, this will point
|
|
// to the new current response fragment so the caller may continue parsing
|
|
// as usual.
|
|
// LPSTR *ppCurrent [in/out] - a pointer to the first '(' after the ENVELOPE
|
|
// tag. On function exit, this pointer is updated to point past the ')'
|
|
// after the ENVELOPE tag so the caller may continue parsing as usual.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseIMAPAddresses(IMAPADDR **ppiaResults,
|
|
IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR *ppCurrent)
|
|
{
|
|
HRESULT hrResult = S_OK;
|
|
BOOL fResult;
|
|
IMAPADDR *piaCurrent;
|
|
LPSTR p;
|
|
|
|
TraceCall("CImap4Agent::ParseIMAPAddresses");
|
|
|
|
// Initialize output
|
|
*ppiaResults = NULL;
|
|
p = *ppCurrent;
|
|
|
|
// ppCurrent either points to an address list, or "NIL"
|
|
if ('(' != *p)
|
|
{
|
|
int iResult;
|
|
|
|
// Check for "NIL"
|
|
iResult = StrCmpNI(p, "NIL", 3);
|
|
if (0 == iResult) {
|
|
hrResult = S_OK;
|
|
p += 3; // Skip past NIL
|
|
}
|
|
else
|
|
hrResult = TraceResult(IXP_E_IMAP_SVR_SYNTAXERR);
|
|
|
|
goto exit;
|
|
}
|
|
else
|
|
p += 1; // Skip opening parenthesis
|
|
|
|
// Loop over all addresses
|
|
piaCurrent = NULL;
|
|
while ('\0' != *p && ')' != *p) {
|
|
|
|
// Skip any whitespace
|
|
while (cSPACE == *p)
|
|
p += 1;
|
|
|
|
// Skip opening parenthesis
|
|
Assert('(' == *p);
|
|
p += 1;
|
|
|
|
// Allocate a structure to hold current address
|
|
if (NULL == piaCurrent) {
|
|
fResult = MemAlloc((void **)ppiaResults, sizeof(IMAPADDR));
|
|
piaCurrent = *ppiaResults;
|
|
}
|
|
else {
|
|
fResult = MemAlloc((void **)&piaCurrent->pNext, sizeof(IMAPADDR));
|
|
piaCurrent = piaCurrent->pNext;
|
|
}
|
|
|
|
if (FALSE == fResult)
|
|
{
|
|
hrResult = TraceResult(E_OUTOFMEMORY);
|
|
goto exit;
|
|
}
|
|
|
|
ZeroMemory(piaCurrent, sizeof(IMAPADDR));
|
|
|
|
// (1) Parse addr_name (see RFC2060)
|
|
hrResult = NStringToString(ppilfLine, &piaCurrent->pszName, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (2) Parse addr_adl (see RFC2060)
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &piaCurrent->pszADL, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (3) Parse addr_mailbox (see RFC2060)
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &piaCurrent->pszMailbox, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// (4) Parse addr_host (see RFC2060)
|
|
Assert(cSPACE == *p);
|
|
p += 1;
|
|
hrResult = NStringToString(ppilfLine, &piaCurrent->pszHost, NULL, &p);
|
|
if (FAILED(hrResult))
|
|
{
|
|
TraceResult(hrResult);
|
|
goto exit;
|
|
}
|
|
|
|
// Skip closing parenthesis
|
|
Assert(')' == *p);
|
|
p += 1;
|
|
|
|
} // while
|
|
|
|
// Read past closing parenthesis
|
|
Assert(')' == *p);
|
|
p += 1;
|
|
|
|
exit:
|
|
if (FAILED(hrResult))
|
|
{
|
|
FreeIMAPAddresses(*ppiaResults);
|
|
*ppiaResults = NULL;
|
|
}
|
|
|
|
*ppCurrent = p;
|
|
return hrResult;
|
|
} // ParseIMAPAddresses
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DowngradeFetchResponse
|
|
//
|
|
// Purpose:
|
|
// For IIMAPTransport users who do not enable FETCH_CMD_RESULTS_EX structures
|
|
// via IIMAPTransport2::EnableFetchEx, we have to continue to report FETCH
|
|
// results using FETCH_CMD_RESULTS. This function copies the relevant data
|
|
// from a FETCH_CMD_RESULTS_EX structure to FETCH_CMD_RESULTS. Too bad IDL
|
|
// doesn't support inheritance in structures...
|
|
//
|
|
// Arguments:
|
|
// FETCH_CMD_RESULTS *pcfrOldFetchStruct [out] - points to destination for
|
|
// data contained in pfcreNewFetchStruct.
|
|
// FETCH_CMD_RESULTS_EX *pfcreNewFetchStruct [in] - points to source data
|
|
// which is to be transferred to pfcrOldFetchStruct.
|
|
//***************************************************************************
|
|
void CImap4Agent::DowngradeFetchResponse(FETCH_CMD_RESULTS *pfcrOldFetchStruct,
|
|
FETCH_CMD_RESULTS_EX *pfcreNewFetchStruct)
|
|
{
|
|
pfcrOldFetchStruct->dwMsgSeqNum = pfcreNewFetchStruct->dwMsgSeqNum;
|
|
pfcrOldFetchStruct->bMsgFlags = pfcreNewFetchStruct->bMsgFlags;
|
|
pfcrOldFetchStruct->mfMsgFlags = pfcreNewFetchStruct->mfMsgFlags;
|
|
|
|
pfcrOldFetchStruct->bRFC822Size = pfcreNewFetchStruct->bRFC822Size;
|
|
pfcrOldFetchStruct->dwRFC822Size = pfcreNewFetchStruct->dwRFC822Size;
|
|
|
|
pfcrOldFetchStruct->bUID = pfcreNewFetchStruct->bUID;
|
|
pfcrOldFetchStruct->dwUID = pfcreNewFetchStruct->dwUID;
|
|
|
|
pfcrOldFetchStruct->bInternalDate = pfcreNewFetchStruct->bInternalDate;
|
|
pfcrOldFetchStruct->ftInternalDate = pfcreNewFetchStruct->ftInternalDate;
|
|
|
|
pfcrOldFetchStruct->lpFetchCookie1 = pfcreNewFetchStruct->lpFetchCookie1;
|
|
pfcrOldFetchStruct->lpFetchCookie2 = pfcreNewFetchStruct->lpFetchCookie2;
|
|
} // DowngradeFetchResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: QuotedToString
|
|
//
|
|
// Purpose:
|
|
// This function, given a "quoted" (see RFC1730, Formal Syntax), converts
|
|
// it to a regular string, that is, a character array without any escape
|
|
// characters or delimiting double quotes. For instance, the quoted,
|
|
// "\"FUNKY\"\\MAN!!!!" would be converted to "FUNKY"\MAN!!!!.
|
|
//
|
|
// Arguments:
|
|
// LPSTR *ppszDestination [out] - the translated quoted is returned as
|
|
// a regular string in this destination buffer. It is the caller's
|
|
// responsibility to MemFree this buffer when finished with it.
|
|
// LPDWORD pdwLengthOfDestination [out] - the length of *ppszDestination is
|
|
// returned here. Pass NULL if not interested.
|
|
// LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the quoted,
|
|
// including opening and closing double-quotes. The function returns
|
|
// a pointer to the end of the quoted so that the caller may continue
|
|
// parsing the response line.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If successful, returns
|
|
// hrIMAP_S_QUOTED.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::QuotedToString(LPSTR *ppszDestination,
|
|
LPDWORD pdwLengthOfDestination,
|
|
LPSTR *ppCurrentSrcPos)
|
|
{
|
|
LPSTR lpszSourceBuf, lpszUnescapedSequence;
|
|
CByteStream bstmQuoted;
|
|
int iUnescapedSequenceLen;
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppszDestination);
|
|
Assert(NULL != ppCurrentSrcPos);
|
|
Assert(NULL != *ppCurrentSrcPos);
|
|
|
|
lpszSourceBuf = *ppCurrentSrcPos;
|
|
if ('\"' != *lpszSourceBuf)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR; // Need opening double-quote
|
|
|
|
// Walk through string, translating escape characters as we go
|
|
lpszSourceBuf += 1;
|
|
lpszUnescapedSequence = lpszSourceBuf;
|
|
while('\"' != *lpszSourceBuf && '\0' != *lpszSourceBuf) {
|
|
if ('\\' == *lpszSourceBuf) {
|
|
char cEscaped;
|
|
|
|
// Escape character found, get next character
|
|
iUnescapedSequenceLen = (int) (lpszSourceBuf - lpszUnescapedSequence);
|
|
lpszSourceBuf += 1;
|
|
|
|
switch(*lpszSourceBuf) {
|
|
case '\\':
|
|
cEscaped = '\\';
|
|
break;
|
|
|
|
case '\"':
|
|
cEscaped = '\"';
|
|
break;
|
|
|
|
default:
|
|
// (Includes case '\0':)
|
|
// This isn't a spec'ed escape char!
|
|
// Return syntax error, but consider robust course of action $REVIEW
|
|
Assert(FALSE);
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
} // switch(*lpszSourceBuf)
|
|
|
|
// First, flush unescaped sequence leading up to escape sequence
|
|
if (iUnescapedSequenceLen > 0) {
|
|
hrResult = bstmQuoted.Write(lpszUnescapedSequence,
|
|
iUnescapedSequenceLen, NULL);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
}
|
|
|
|
// Append escaped character
|
|
hrResult = bstmQuoted.Write(&cEscaped, 1, NULL);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
// Set us up to find next unescaped sequence
|
|
lpszUnescapedSequence = lpszSourceBuf + 1;
|
|
} // if ('\' == *lpszSourceBuf)
|
|
else if (FALSE == isTEXT_CHAR(*lpszSourceBuf))
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
lpszSourceBuf += 1;
|
|
} // while not closing quote or end of string
|
|
|
|
// Flush any remaining unescaped sequences
|
|
iUnescapedSequenceLen = (int) (lpszSourceBuf - lpszUnescapedSequence);
|
|
if (iUnescapedSequenceLen > 0) {
|
|
hrResult = bstmQuoted.Write(lpszUnescapedSequence, iUnescapedSequenceLen, NULL);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
}
|
|
|
|
*ppCurrentSrcPos = lpszSourceBuf + 1; // Update user's ptr to point PAST quoted
|
|
if ('\0' == *lpszSourceBuf)
|
|
return IXP_E_IMAP_SVR_SYNTAXERR; // Quoted str ended before closing quote!
|
|
else {
|
|
hrResult = bstmQuoted.HrAcquireStringA(pdwLengthOfDestination,
|
|
ppszDestination, ACQ_DISPLACE);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
else
|
|
return hrIMAP_S_QUOTED;
|
|
}
|
|
} // Convert QuotedToString
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AStringToString
|
|
//
|
|
// Purpose:
|
|
// This function, given an astring (see RFC1730, Formal Syntax), converts
|
|
// it to a regular string, that is, a character array without any escape
|
|
// characters or delimiting double quotes or literal size specifications.
|
|
// As specified in RFC1730, an astring may be expressed as an atom, a
|
|
// quoted, or a literal.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
|
|
// IMAP response fragment. This is used to retrieve the next fragment
|
|
// in the chain (literal or line) since astrings can be sent as literals.
|
|
// This pointer is always updated to point to the fragment currently in
|
|
// use, so that the caller may free the last one himself.
|
|
// LPSTR *ppszDestination [out] - the translated astring is returned as a
|
|
// regular string in this destination buffer. It is the caller's
|
|
// responsibility to MemFree the returned buffer when finished with it.
|
|
// LPDWORD pdwLengthOfDestination [in] - the length of *ppszDestination.
|
|
// Pass NULL if not interested.
|
|
// LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the astring,
|
|
// including opening and closing double-quotes if it's a quoted, or the
|
|
// literal size specifier (ie, {#}) if it's a literal. A pointer to the
|
|
// end of the astring is returned to the caller, so that they may
|
|
// continue parsing the response line.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Success codes include:
|
|
// hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
|
|
// hrIMAP_S_QUOTED - a quoted was found and copied to destination
|
|
// hrIMAP_S_ATOM - an atom was found and copied to destination
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::AStringToString(IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR *ppszDestination,
|
|
LPDWORD pdwLengthOfDestination,
|
|
LPSTR *ppCurrentSrcPos)
|
|
{
|
|
LPSTR pSrc;
|
|
|
|
// Check args
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != ppszDestination);
|
|
Assert(NULL != ppCurrentSrcPos);
|
|
Assert(NULL != *ppCurrentSrcPos);
|
|
|
|
// Identify astring as atom, quoted or literal
|
|
pSrc = *ppCurrentSrcPos;
|
|
switch(*pSrc) {
|
|
case '{': {
|
|
IMAP_LINE_FRAGMENT *pilfLiteral, *pilfLine;
|
|
|
|
// It's a literal
|
|
// $REVIEW: We ignore the literal size spec and anything after it. Should we?
|
|
pilfLiteral = (*ppilfLine)->pilfNextFragment;
|
|
if (NULL == pilfLiteral)
|
|
return IXP_E_IMAP_INCOMPLETE_LINE;
|
|
|
|
Assert(iltLITERAL == pilfLiteral->iltFragmentType);
|
|
if (ilsSTRING == pilfLiteral->ilsLiteralStoreType) {
|
|
if (ppszDestination)
|
|
*ppszDestination = PszDupA(pilfLiteral->data.pszSource);
|
|
if (pdwLengthOfDestination)
|
|
*pdwLengthOfDestination = lstrlen(pilfLiteral->data.pszSource);
|
|
}
|
|
else {
|
|
HRESULT hrResult;
|
|
LPSTREAM pstmSource = pilfLiteral->data.pstmSource;
|
|
|
|
// Append a null-terminator to stream
|
|
hrResult = pstmSource->Write(c_szEmpty, 1, NULL);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
// Copy stream into a memory block
|
|
hrResult = HrStreamToByte(pstmSource, (LPBYTE *)ppszDestination,
|
|
pdwLengthOfDestination);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
if (pdwLengthOfDestination)
|
|
*pdwLengthOfDestination -= 1; // includes null-term, so decrease by 1
|
|
}
|
|
|
|
|
|
// OK, now set up next line so caller may continue parsing the response
|
|
pilfLine = pilfLiteral->pilfNextFragment;
|
|
if (NULL == pilfLine)
|
|
return IXP_E_IMAP_INCOMPLETE_LINE;
|
|
|
|
// Update user's pointer into the source line
|
|
Assert(iltLINE == pilfLine->iltFragmentType);
|
|
*ppCurrentSrcPos = pilfLine->data.pszSource;
|
|
|
|
// Clean up and exit
|
|
FreeFragment(&pilfLiteral);
|
|
FreeFragment(ppilfLine);
|
|
*ppilfLine = pilfLine; // Update this ptr so it always points to LAST fragment
|
|
|
|
return hrIMAP_S_FOUNDLITERAL;
|
|
} // case AString == LITERAL
|
|
|
|
case '\"':
|
|
// It's a QUOTED STING, convert it to regular string
|
|
return QuotedToString(ppszDestination, pdwLengthOfDestination,
|
|
ppCurrentSrcPos);
|
|
|
|
default: {
|
|
DWORD dwLengthOfAtom;
|
|
|
|
// It's an atom: find the end of the atom
|
|
while (isATOM_CHAR(*pSrc))
|
|
pSrc += 1;
|
|
|
|
// Copy the atom into a buffer for the user
|
|
dwLengthOfAtom = (DWORD) (pSrc - *ppCurrentSrcPos);
|
|
if (ppszDestination) {
|
|
BOOL fResult;
|
|
|
|
fResult = MemAlloc((void **)ppszDestination, dwLengthOfAtom + 1);
|
|
if (FALSE == fResult)
|
|
return E_OUTOFMEMORY;
|
|
|
|
CopyMemory(*ppszDestination, *ppCurrentSrcPos, dwLengthOfAtom);
|
|
(*ppszDestination)[dwLengthOfAtom] = '\0';
|
|
}
|
|
|
|
if (pdwLengthOfDestination)
|
|
*pdwLengthOfDestination = dwLengthOfAtom;
|
|
|
|
// Update user's pointer
|
|
*ppCurrentSrcPos = pSrc;
|
|
return hrIMAP_S_ATOM;
|
|
} // case AString == ATOM
|
|
} // switch(*pSrc)
|
|
} // AStringToString
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isTEXT_CHAR
|
|
//
|
|
// Purpose:
|
|
// This function identifies characters which are TEXT_CHARs as defined in
|
|
// RFC1730's Formal Syntax section.
|
|
//
|
|
// Returns:
|
|
// This function returns TRUE if the given character fits the definition.
|
|
//***************************************************************************
|
|
inline boolean CImap4Agent::isTEXT_CHAR(char c)
|
|
{
|
|
// $REVIEW: signed/unsigned char, 8/16-bit char issues with 8th bit check
|
|
// Assert(FALSE);
|
|
if (c != (c & 0x7F) || // 7-bit
|
|
'\0' == c ||
|
|
'\r' == c ||
|
|
'\n' == c)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
} // isTEXT_CHAR
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isATOM_CHAR
|
|
//
|
|
// Purpose:
|
|
// This function identifies characters which are ATOM_CHARs as defined in
|
|
// RFC1730's Formal Syntax section.
|
|
//
|
|
// Returns:
|
|
// This function returns TRUE if the given character fits the definition.
|
|
//***************************************************************************
|
|
inline boolean CImap4Agent::isATOM_CHAR(char c)
|
|
{
|
|
// $REVIEW: signed/unsigned char, 8/16-bit char issues with 8th bit check
|
|
// Assert(FALSE);
|
|
if (c != (c & 0x7F) || // 7-bit
|
|
'\0' == c || // At this point, we know it's a CHAR
|
|
'(' == c || // Explicit atom_specials char
|
|
')' == c || // Explicit atom_specials char
|
|
'{' == c || // Explicit atom_specials char
|
|
cSPACE == c || // Explicit atom_specials char
|
|
c < 0x1f || // Check for CTL
|
|
0x7f == c || // Check for CTL
|
|
'%' == c || // Check for list_wildcards
|
|
'*' == c || // Check for list_wildcards
|
|
'\\' == c || // Check for quoted_specials
|
|
'\"' == c) // Check for quoted_specials
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
} // isATOM_CHAR
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: NStringToString
|
|
//
|
|
// Purpose:
|
|
// This function, given an nstring (see RFC1730, Formal Syntax), converts
|
|
// it to a regular string, that is, a character array without any escape
|
|
// characters or delimiting double quotes or literal size specifications.
|
|
// As specified in RFC1730, an nstring may be expressed as a quoted,
|
|
// a literal, or "NIL".
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
|
|
// IMAP response fragment. This is used to retrieve the next fragment
|
|
// in the chain (literal or line) since nstrings can be sent as literals.
|
|
// This pointer is always updated to point to the fragment currently in
|
|
// use, so that the caller may free the last one himself.
|
|
// LPSTR *ppszDestination [out] - the translated nstring is returned as
|
|
// a regular string in this destination buffer. It is the caller's
|
|
// responsibility to MemFree this buffer when finished with it.
|
|
// LPDWORD pdwLengthOfDestination [out] - the length of *ppszDestination is
|
|
// returned here. Pass NULL if not interested.
|
|
// LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the nstring,
|
|
// including opening and closing double-quotes if it's a quoted, or the
|
|
// literal size specifier (ie, {#}) if it's a literal. A pointer to the
|
|
// end of the nstring is returned to the caller, so that they may
|
|
// continue parsing the response line.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Success codes include:
|
|
// hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
|
|
// hrIMAP_S_QUOTED - a quoted was found and copied to destination
|
|
// hrIMAP_S_NIL_NSTRING - "NIL" was found.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::NStringToString(IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTR *ppszDestination,
|
|
LPDWORD pdwLengthOfDestination,
|
|
LPSTR *ppCurrentSrcPos)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != ppszDestination);
|
|
Assert(NULL != ppCurrentSrcPos);
|
|
Assert(NULL != *ppCurrentSrcPos);
|
|
|
|
// nstrings are almost exactly like astrings, but nstrings cannot
|
|
// have any value other than "NIL" expressed as an atom.
|
|
hrResult = AStringToString(ppilfLine, ppszDestination, pdwLengthOfDestination,
|
|
ppCurrentSrcPos);
|
|
|
|
// If AStringToString found an ATOM, the only acceptable response is "NIL"
|
|
if (hrIMAP_S_ATOM == hrResult) {
|
|
if (0 == lstrcmpi("NIL", *ppszDestination)) {
|
|
**ppszDestination = '\0'; // Blank str in case someone tries to use it
|
|
if (pdwLengthOfDestination)
|
|
*pdwLengthOfDestination = 0;
|
|
|
|
return hrIMAP_S_NIL_NSTRING;
|
|
}
|
|
else {
|
|
MemFree(*ppszDestination);
|
|
*ppszDestination = NULL;
|
|
if (pdwLengthOfDestination)
|
|
*pdwLengthOfDestination = 0;
|
|
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
}
|
|
}
|
|
else
|
|
return hrResult;
|
|
} // NStringToString
|
|
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: NStringToStream
|
|
//
|
|
// Purpose:
|
|
// This function is performs exactly the same job as NStringToString, but
|
|
// places the result in a stream, instead. This function should be used when
|
|
// the caller expects potentially LARGE results.
|
|
//
|
|
// Arguments:
|
|
// Similar to NStringToString (minus string buffer output args), plus:
|
|
// LPSTREAM *ppstmResult [out] - A stream is created for the caller, and
|
|
// the translated nstring is written as a regular string to the stream
|
|
// and returned via this argument. The returned stream is not rewound
|
|
// on exit.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Success codes include:
|
|
// hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
|
|
// hrIMAP_S_QUOTED - a quoted was found and copied to destination
|
|
// hrIMAP_S_NIL_NSTRING - "NIL" was found.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::NStringToStream(IMAP_LINE_FRAGMENT **ppilfLine,
|
|
LPSTREAM *ppstmResult,
|
|
LPSTR *ppCurrentSrcPos)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppilfLine);
|
|
Assert(NULL != *ppilfLine);
|
|
Assert(NULL != ppstmResult);
|
|
Assert(NULL != ppCurrentSrcPos);
|
|
Assert(NULL != *ppCurrentSrcPos);
|
|
|
|
// Check if this nstring is a literal
|
|
if ('{' == **ppCurrentSrcPos) {
|
|
IMAP_LINE_FRAGMENT *pilfLine, *pilfLiteral;
|
|
|
|
// Yup, it's a literal! Write the literal to a stream
|
|
// $REVIEW: We ignore the literal size spec and anything after it. Should we?
|
|
pilfLiteral = (*ppilfLine)->pilfNextFragment;
|
|
if (NULL == pilfLiteral)
|
|
return IXP_E_IMAP_INCOMPLETE_LINE;
|
|
|
|
Assert(iltLITERAL == pilfLiteral->iltFragmentType);
|
|
if (ilsSTRING == pilfLiteral->ilsLiteralStoreType) {
|
|
HRESULT hrStreamResult;
|
|
ULONG ulNumBytesWritten;
|
|
|
|
// Literal is stored as string. Create stream and write to it
|
|
hrStreamResult = MimeOleCreateVirtualStream(ppstmResult);
|
|
if (FAILED(hrStreamResult))
|
|
return hrStreamResult;
|
|
|
|
hrStreamResult = (*ppstmResult)->Write(pilfLiteral->data.pszSource,
|
|
pilfLiteral->dwLengthOfFragment, &ulNumBytesWritten);
|
|
if (FAILED(hrStreamResult))
|
|
return hrStreamResult;
|
|
|
|
Assert(ulNumBytesWritten == pilfLiteral->dwLengthOfFragment);
|
|
}
|
|
else {
|
|
// Literal is stored as stream. Just AddRef() and return ptr
|
|
(pilfLiteral->data.pstmSource)->AddRef();
|
|
*ppstmResult = pilfLiteral->data.pstmSource;
|
|
}
|
|
|
|
// No need to null-terminate streams
|
|
|
|
// OK, now set up next line fragment so caller may continue parsing response
|
|
pilfLine = pilfLiteral->pilfNextFragment;
|
|
if (NULL == pilfLine)
|
|
return IXP_E_IMAP_INCOMPLETE_LINE;
|
|
|
|
// Update user's pointer into the source line
|
|
Assert(iltLINE == pilfLine->iltFragmentType);
|
|
*ppCurrentSrcPos = pilfLine->data.pszSource;
|
|
|
|
// Clean up and exit
|
|
FreeFragment(&pilfLiteral);
|
|
FreeFragment(ppilfLine);
|
|
*ppilfLine = pilfLine; // Update this ptr so it always points to LAST fragment
|
|
|
|
return hrIMAP_S_FOUNDLITERAL;
|
|
}
|
|
else {
|
|
HRESULT hrResult, hrStreamResult;
|
|
ULONG ulLiteralLen, ulNumBytesWritten;
|
|
LPSTR pszLiteralSrc;
|
|
|
|
// Not a literal. Translate NString to string (in-place).
|
|
// Add 1 to destination size calculation for null-terminator
|
|
hrResult = NStringToString(ppilfLine, &pszLiteralSrc,
|
|
&ulLiteralLen, ppCurrentSrcPos);
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
// Create stream to hold result
|
|
hrStreamResult = MimeOleCreateVirtualStream(ppstmResult);
|
|
if (FAILED(hrStreamResult)) {
|
|
MemFree(pszLiteralSrc);
|
|
return hrStreamResult;
|
|
}
|
|
|
|
// Write the result to the stream
|
|
hrStreamResult = (*ppstmResult)->Write(pszLiteralSrc, ulLiteralLen,
|
|
&ulNumBytesWritten);
|
|
MemFree(pszLiteralSrc);
|
|
if (FAILED(hrStreamResult))
|
|
return hrStreamResult;
|
|
|
|
Assert(ulLiteralLen == ulNumBytesWritten); // Debug-only paranoia
|
|
return hrResult;
|
|
}
|
|
} // NStringToStream
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ParseMsgFlagList
|
|
//
|
|
// Purpose:
|
|
// Given a flag_list (see RFC1730, Formal Syntax section), this function
|
|
// returns the IMAP_MSG_* bit-flags which correspond to the flags in the
|
|
// list. For instance, given the flag list, "(\Answered \Draft)", this
|
|
// function returns IMAP_MSG_ANSWERED | IMAP_MSG_DRAFT. Any unrecognized
|
|
// flags are ignored.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszStartOfFlagList [in/out] - a pointer the start of a flag_list,
|
|
// including opening and closing parentheses. This function does not
|
|
// explicitly output anything to this string, but it does MODIFY the
|
|
// string by null-terminating spaces and the closing parenthesis.
|
|
// IMAP_MSGFLAGS *lpmfMsgFlags [out] - IMAP_MSGFLAGS value corresponding
|
|
// to the given flag list. If the given flag list is empty, this
|
|
// function returns IMAP_MSG_NOFLAGS.
|
|
// LPDWORD lpdwNumBytesRead [out] - the number of bytes between the
|
|
// opening parenthesis and the closing parenthesis of the flag list.
|
|
// Adding this number to the address of the start of the flag list
|
|
// yields a pointer to the closing parenthesis.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ParseMsgFlagList(LPSTR lpszStartOfFlagList,
|
|
IMAP_MSGFLAGS *lpmfMsgFlags,
|
|
LPDWORD lpdwNumBytesRead)
|
|
{
|
|
LPSTR p, lpszEndOfFlagList, pszTok;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszStartOfFlagList);
|
|
Assert(NULL != lpmfMsgFlags);
|
|
Assert(NULL != lpdwNumBytesRead);
|
|
|
|
p = lpszStartOfFlagList;
|
|
if ('(' != *p)
|
|
// Opening parenthesis was not found
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
// Look for closing parenthesis Assert(FALSE); // *** $REVIEW: C-RUNTIME ALERT
|
|
lpszEndOfFlagList = StrChr(p, ')');
|
|
if (NULL == lpszEndOfFlagList)
|
|
// Closing parenthesis was not found
|
|
return IXP_E_IMAP_SVR_SYNTAXERR;
|
|
|
|
|
|
*lpdwNumBytesRead = (DWORD) (lpszEndOfFlagList - lpszStartOfFlagList);
|
|
*lpszEndOfFlagList = '\0'; // Null-terminate flag list
|
|
*lpmfMsgFlags = IMAP_MSG_NOFLAGS; // Initialize output
|
|
pszTok = lpszStartOfFlagList + 1;
|
|
p = StrTokEx(&pszTok, g_szSpace); // Get ptr to first token
|
|
|
|
while (NULL != p) {
|
|
// We'll narrow the search down for the flag by looking at its
|
|
// first letter. Although there's a conflict between \Deleted and
|
|
// \Draft, this is the best way for case-insensitive search
|
|
// (first non-conflicting letter is five characters in!)
|
|
|
|
// First, check that there is at least one character
|
|
if ('\\' == *p) {
|
|
p += 1;
|
|
switch (*p) {
|
|
int iResult;
|
|
|
|
case 'a':
|
|
case 'A': // Possible "Answered" flag
|
|
iResult = lstrcmpi(p, c_szIMAP_MSG_ANSWERED);
|
|
if (0 == iResult)
|
|
*lpmfMsgFlags |= IMAP_MSG_ANSWERED; // Definitely the \Answered flag
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F': // Possible "Flagged" flag
|
|
iResult = lstrcmpi(p, c_szIMAP_MSG_FLAGGED);
|
|
if (0 == iResult)
|
|
*lpmfMsgFlags |= IMAP_MSG_FLAGGED; // Definitely the \Flagged flag
|
|
break;
|
|
|
|
case 'd':
|
|
case 'D': // Possible "Deleted" or "Draft" flags
|
|
// "Deleted" is more probable, so check it first
|
|
iResult = lstrcmpi(p, c_szIMAP_MSG_DELETED);
|
|
if (0 == iResult) {
|
|
*lpmfMsgFlags |= IMAP_MSG_DELETED; // Definitely the \Deleted flag
|
|
break;
|
|
}
|
|
|
|
iResult = lstrcmpi(p, c_szIMAP_MSG_DRAFT);
|
|
if (0 == iResult) {
|
|
*lpmfMsgFlags |= IMAP_MSG_DRAFT; // Definitely the \Draft flag
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 's':
|
|
case 'S': // Possible "Seen" flags
|
|
iResult = lstrcmpi(p, c_szIMAP_MSG_SEEN);
|
|
if (0 == iResult)
|
|
*lpmfMsgFlags |= IMAP_MSG_SEEN; // Definitely the \Seen flag
|
|
|
|
break;
|
|
} // switch(*p)
|
|
} // if ('\\' == *p)
|
|
|
|
p = StrTokEx(&pszTok, g_szSpace); // Grab next token
|
|
} // while (NULL != p)
|
|
|
|
return S_OK; // If we hit this point, we're all done
|
|
} // ParseMsgFlagList
|
|
|
|
|
|
|
|
//****************************************************************************
|
|
// Function: AppendSendAString
|
|
//
|
|
// Purpose:
|
|
// This function is intended to be used by a caller who is constructing a
|
|
// command line which contains IMAP astrings (see RFC1730 Formal Syntax).
|
|
// This function takes a regular C string and converts it to an IMAP astring,
|
|
// appending it to the end of the command line under construction.
|
|
//
|
|
// An astring may take the form of an atom, a quoted, or a literal. For
|
|
// performance reasons (both conversion and network), I don't see any reason
|
|
// we should ever output an atom. Thus, this function returns either a quoted
|
|
// or a literal.
|
|
//
|
|
// Although IMAP's most expressive form of astring is the literal, it can
|
|
// result in costly network handshaking between client and server, and
|
|
// thus should be avoided unless required. Another consideration to use
|
|
// in deciding to use literal/quoted is size of the string. Most IMAP servers
|
|
// will have some internal limit to the maximum length of a line. To avoid
|
|
// exceeding this limit, it is wise to encode large strings as literals
|
|
// (where large typically means 1024 bytes).
|
|
//
|
|
// If the function converts the C string to a quoted, it appends it to the
|
|
// end of the partially-constructed command line. If it must send as a literal,
|
|
// it enqueues the partially-constructed command line in the send queue of the
|
|
// command-in-progress, enqueues the literal as well, then creates a new line
|
|
// fragment so the caller may continue constructing the command. The caller's
|
|
// pointer to the end of the command line is reset so that the user may
|
|
// append the next argument without concern of whether the C string
|
|
// was sent as a quoted or a literal. Although the caller may pretend
|
|
// that he's constructing a command line simply by appending to it, when this
|
|
// function returns, he caller may not be appending to the same string buffer.
|
|
// (Not that the caller should care.)
|
|
//
|
|
// This function prepends a SPACE by default, so this function may be called
|
|
// as many times in a row as desired. Each astring will be separated by a
|
|
// space.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - a pointer to the command currently under
|
|
// construction. This argument is needed so we can enqueue command
|
|
// fragments to the command's send queue.
|
|
// LPSTR lpszCommandLine [in] - a pointer to a partially constructed
|
|
// command line suitable for passing to SendCmdLine (which supplies the
|
|
// tag). For instance, this argument could point to a string, "SELECT".
|
|
// LPSTR *ppCmdLinePos [in/out] - a pointer to the end of the command
|
|
// line. If this function converts the C string to a quoted, the quoted
|
|
// is appended to lpszCommandLine, and *ppCmdLinePos is updated to point
|
|
// to the end of the quoted. If the C string is converted to a literal,
|
|
// lpszCommandLine is made blank (null-terminated), and *ppCmdLinePos
|
|
// is reset to the start of the line. In either case, the user should
|
|
// continue to construct the command line using the updated *ppCmdLinePos
|
|
// pointer, and send lpszCommandLine as usual to SendCmdLine.
|
|
// DWORD dwSizeOfCommandLine [in] - size of the command line buffer, for
|
|
// buffer overflow-checking purposes.
|
|
// LPSTR lpszSource [in] - pointer to the source string.
|
|
// BOOL fPrependSpace [in] - TRUE if we should prepend a space, FALSE if no
|
|
// space should be prepended. Usually TRUE unless this AString follows
|
|
// a rangelist.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. In particular, there are two
|
|
// success codes (which the caller need not act on):
|
|
//
|
|
// hrIMAP_S_QUOTED - indicates that the source string was successfully
|
|
// converted to a quoted, and has been appended to lpszCommandLine.
|
|
// *ppCmdLinePos has been updated to point to the end of the new line
|
|
// should the caller wish to continue appending arguments.
|
|
// hrIMAP_S_FOUNDLITERAL - indicates that the source string was
|
|
// sent as a literal. The command line has been blanked, and the user
|
|
// may continue constructing the command line with his *ppCmdLinePos ptr.
|
|
//****************************************************************************
|
|
HRESULT CImap4Agent::AppendSendAString(CIMAPCmdInfo *piciCommand,
|
|
LPSTR lpszCommandLine, LPSTR *ppCmdLinePos,
|
|
DWORD dwSizeOfCommandLine, LPCSTR lpszSource,
|
|
BOOL fPrependSpace)
|
|
{
|
|
HRESULT hrResult;
|
|
DWORD dwMaxQuotedSize;
|
|
DWORD dwSizeOfQuoted;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
Assert(NULL != lpszCommandLine);
|
|
Assert(NULL != ppCmdLinePos);
|
|
Assert(NULL != *ppCmdLinePos);
|
|
Assert(0 != dwSizeOfCommandLine);
|
|
Assert(NULL != lpszSource);
|
|
Assert(*ppCmdLinePos < lpszCommandLine + dwSizeOfCommandLine);
|
|
|
|
|
|
// Assume quoted string at start. If we have to send as literal, then too
|
|
// bad, the quoted conversion work is wasted.
|
|
|
|
// Prepend a space if so directed by user
|
|
if (fPrependSpace) {
|
|
**ppCmdLinePos = cSPACE;
|
|
*ppCmdLinePos += 1;
|
|
}
|
|
|
|
dwMaxQuotedSize = min(dwLITERAL_THRESHOLD,
|
|
(DWORD) (lpszCommandLine + dwSizeOfCommandLine - *ppCmdLinePos));
|
|
hrResult = StringToQuoted(*ppCmdLinePos, lpszSource, dwMaxQuotedSize,
|
|
&dwSizeOfQuoted);
|
|
|
|
// Always check for buffer overflow
|
|
Assert(*ppCmdLinePos + dwSizeOfQuoted < lpszCommandLine + dwSizeOfCommandLine);
|
|
|
|
if (SUCCEEDED(hrResult)) {
|
|
Assert(hrIMAP_S_QUOTED == hrResult);
|
|
|
|
// Successfully converted to quoted,
|
|
*ppCmdLinePos += dwSizeOfQuoted; // Advance user's ptr into cmd line
|
|
}
|
|
else {
|
|
BOOL bResult;
|
|
DWORD dwLengthOfLiteral;
|
|
DWORD dwLengthOfLiteralSpec;
|
|
IMAP_LINE_FRAGMENT *pilfLiteral;
|
|
|
|
// OK, couldn't convert to quoted (buffer overflow? 8-bit char?)
|
|
// Looks like it's literal time. We SEND this puppy.
|
|
|
|
// Find out length of literal, append to command line and send
|
|
dwLengthOfLiteral = lstrlen(lpszSource); // Yuck, but I'm betting most Astrings are quoted
|
|
dwLengthOfLiteralSpec = wnsprintf(*ppCmdLinePos, dwSizeOfCommandLine - (DWORD)(*ppCmdLinePos - lpszCommandLine),
|
|
"{%lu}\r\n", dwLengthOfLiteral);
|
|
Assert(*ppCmdLinePos + dwLengthOfLiteralSpec < lpszCommandLine + dwSizeOfCommandLine);
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, lpszCommandLine,
|
|
(DWORD) (*ppCmdLinePos + dwLengthOfLiteralSpec - lpszCommandLine)); // Send entire command line
|
|
if (FAILED(hrResult))
|
|
return hrResult;
|
|
|
|
// Queue the literal up - send FSM will wait for cmd continuation
|
|
pilfLiteral = new IMAP_LINE_FRAGMENT;
|
|
pilfLiteral->iltFragmentType = iltLITERAL;
|
|
pilfLiteral->ilsLiteralStoreType = ilsSTRING;
|
|
pilfLiteral->dwLengthOfFragment = dwLengthOfLiteral;
|
|
pilfLiteral->pilfNextFragment = NULL;
|
|
pilfLiteral->pilfPrevFragment = NULL;
|
|
DWORD cchSize = (dwLengthOfLiteral + 1);
|
|
bResult = MemAlloc((void **) &pilfLiteral->data.pszSource, cchSize * sizeof(pilfLiteral->data.pszSource[0]));
|
|
if (FALSE == bResult)
|
|
{
|
|
delete pilfLiteral;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
StrCpyN(pilfLiteral->data.pszSource, lpszSource, cchSize);
|
|
|
|
EnqueueFragment(pilfLiteral, piciCommand->pilqCmdLineQueue);
|
|
|
|
// Done with sending cmd line w/ literal. Blank out old cmd line and rewind ptr
|
|
*ppCmdLinePos = lpszCommandLine;
|
|
*lpszCommandLine = '\0';
|
|
|
|
hrResult = hrIMAP_S_FOUNDLITERAL;
|
|
} // else: convert AString to Literal
|
|
|
|
return hrResult;
|
|
} // AppendSendAString
|
|
|
|
|
|
|
|
//****************************************************************************
|
|
// Function: StringToQuoted
|
|
//
|
|
// Purpose:
|
|
// This function converts a regular C string to an IMAP quoted (see RFC1730
|
|
// Formal Syntax).
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszDestination [out] - the output buffer where the quoted should
|
|
// be placed.
|
|
// LPSTR lpszSource [in] - the source string.
|
|
// DWORD dwSizeOfDestination [in] - the size of the output buffer,
|
|
// lpszDestination. Note that in the worst case, the size of the output
|
|
// buffer must be at least one character larger than the quoted actually
|
|
// needs. This is because before translating a character from source to
|
|
// destination, the loop checks if there is enough room for the worst
|
|
// case, a quoted_special, which needs 2 bytes.
|
|
// LPDWORD lpdwNumCharsWritten [out] - the number of characters written
|
|
// to the output buffer, not including the null-terminator. Adding this
|
|
// value to lpszDestination will result in a pointer to the end of the
|
|
// quoted.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. In particular, this function
|
|
// returns hrIMAP_S_QUOTED if it was successful in converting the source
|
|
// string to a quoted. If not, the function returns E_FAIL.
|
|
//****************************************************************************
|
|
HRESULT CImap4Agent::StringToQuoted(LPSTR lpszDestination, LPCSTR lpszSource,
|
|
DWORD dwSizeOfDestination,
|
|
LPDWORD lpdwNumCharsWritten)
|
|
{
|
|
LPCSTR p;
|
|
DWORD dwNumBytesWritten;
|
|
|
|
Assert(NULL != lpszDestination);
|
|
Assert(NULL != lpszSource);
|
|
|
|
// Initialize return value
|
|
*lpdwNumCharsWritten = 0;
|
|
|
|
if (dwSizeOfDestination >= 3)
|
|
dwSizeOfDestination -= 2; // Leave room for closing quote and null-term at end
|
|
else {
|
|
Assert(FALSE); // Smallest quoted is 3 chars ('\"\"\0')
|
|
return IXP_E_IMAP_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
p = lpszSource;
|
|
*lpszDestination = '\"'; // Start us off with an opening quote
|
|
lpszDestination += 1;
|
|
dwNumBytesWritten = 1;
|
|
// Keep looping until we hit source null-term, or until we don't have
|
|
// enough room in destination for largest output (2 chars for quoted_special)
|
|
dwSizeOfDestination -= 1; // This ensures always room for quoted_special
|
|
while (dwNumBytesWritten < dwSizeOfDestination && '\0' != *p) {
|
|
|
|
if (FALSE == isTEXT_CHAR(*p))
|
|
return E_FAIL; // Quoted's can only represent TEXT_CHAR's
|
|
|
|
if ('\\' == *p || '\"' == *p) {
|
|
*lpszDestination = '\\'; // Prefix with escape character
|
|
lpszDestination += 1;
|
|
dwNumBytesWritten += 1;
|
|
} // if quoted_special
|
|
|
|
*lpszDestination = *p;
|
|
lpszDestination += 1;
|
|
dwNumBytesWritten += 1;
|
|
p += 1;
|
|
} // while ('\0' != *p)
|
|
|
|
*lpszDestination = '\"'; // Install closing quote
|
|
*(lpszDestination + 1) = '\0'; // Null-terminate the string
|
|
*lpdwNumCharsWritten = dwNumBytesWritten + 1; // Incl closing quote in size
|
|
|
|
if ('\0' == *p)
|
|
return hrIMAP_S_QUOTED;
|
|
else
|
|
return IXP_E_IMAP_BUFFER_OVERFLOW; // Buffer overflow
|
|
} // StringToQuoted
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GenerateCommandTag
|
|
//
|
|
// Purpose:
|
|
// This function generates a unique tag so that a command issuer may
|
|
// identify his command to the IMAP server (and so that the server response
|
|
// may be identified with the command). It is a simple base-36 (alphanumeric)
|
|
// counter which increments a static 4-digit base-36 number on each call.
|
|
// (Digits are 0,1,2,3,4,6,7,8,9,A,B,C,...,Z).
|
|
//
|
|
// Returns:
|
|
// No return value. This function always succeeds.
|
|
//***************************************************************************
|
|
void CImap4Agent::GenerateCommandTag(LPSTR lpszTag)
|
|
{
|
|
static char szCurrentTag[NUM_TAG_CHARS+1] = "ZZZZ";
|
|
LPSTR p;
|
|
boolean bWraparound;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszTag);
|
|
|
|
EnterCriticalSection(&m_csTag);
|
|
|
|
// Increment current tag
|
|
p = szCurrentTag + NUM_TAG_CHARS - 1; // p now points to last tag character
|
|
do {
|
|
bWraparound = FALSE;
|
|
*p += 1;
|
|
|
|
// Increment from '9' should jump to 'A'
|
|
if (*p > '9' && *p < 'A')
|
|
*p = 'A';
|
|
else if (*p > 'Z') {
|
|
// Increment from 'Z' should wrap around to '0'
|
|
*p = '0';
|
|
bWraparound = TRUE;
|
|
p -= 1; // Advance pointer to more significant character
|
|
}
|
|
} while (TRUE == bWraparound && szCurrentTag <= p);
|
|
|
|
LeaveCriticalSection(&m_csTag);
|
|
|
|
// Return result to caller
|
|
StrCpyN(lpszTag, szCurrentTag, TAG_BUFSIZE);
|
|
} // GenerateCommandTag
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function NoArgCommand
|
|
//
|
|
// Purpose:
|
|
// This function can construct a command line for any function of the
|
|
// form: <tag> <command>.
|
|
//
|
|
// This function constructs the command line, sends it out, and returns the
|
|
// result of the send operation.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
|
|
// IMAP_COMMAND icCommandID [in] - the command ID for this command,
|
|
// eg, icCREATE_COMMAND.
|
|
// SERVERSTATE ssMinimumState [in] - minimum server state required for
|
|
// the given command. Used for debug purposes only.
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - wParam and lParam form a unique ID assigned by the
|
|
// caller to this IMAP command and its responses. Can be anything, but
|
|
// note that the value of 0, 0 is reserved for unsolicited responses.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::NoArgCommand(LPCSTR lpszCommandVerb,
|
|
IMAP_COMMAND icCommandID,
|
|
SERVERSTATE ssMinimumState,
|
|
WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
HRESULT hrResult;
|
|
char szBuffer[CMDLINE_BUFSIZE];
|
|
CIMAPCmdInfo *piciCommand;
|
|
DWORD dwCmdLineLen;
|
|
|
|
// Verify proper server state and set us up as current command
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszCommandVerb);
|
|
Assert(icNO_COMMAND != icCommandID);
|
|
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less
|
|
if (ssMinimumState > m_ssServerState &&
|
|
(ssConnecting != m_ssServerState || ssMinimumState > ssAuthenticated)) {
|
|
// Forgive the NOOP command, due to bug #31968 (err msg build-up if svr drops conn)
|
|
AssertSz(icNOOP_COMMAND == icCommandID,
|
|
"The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
piciCommand = new CIMAPCmdInfo(this, icCommandID, ssMinimumState,
|
|
wParam, lParam, pCBHandler);
|
|
if (piciCommand == NULL) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
dwCmdLineLen = wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s\r\n", piciCommand->szTag, lpszCommandVerb);
|
|
|
|
// Send command to server
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, dwCmdLineLen);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
return hrResult;
|
|
} // NoArgCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function OneArgCommand
|
|
//
|
|
// Purpose:
|
|
// This function can construct a command line for any function of the
|
|
// form: <tag> <command> <astring>. This currently includes SELECT, EXAMINE,
|
|
// CREATE, DELETE, SUBSCRIBE and UNSUBSCRIBE. Since all of these commands
|
|
// require the server to be in Authorized state, I don't bother asking for
|
|
// a minimum SERVERSTATE argument.
|
|
//
|
|
// This function constructs the command line, sends it out, and returns the
|
|
// result of the send operation.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
|
|
// LPSTR lpszMboxName [in] - a C string representing the argument for the
|
|
// command. It is automatically converted to an IMAP astring.
|
|
// IMAP_COMMAND icCommandID [in] - the command ID for this command,
|
|
// eg, icCREATE_COMMAND.
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::OneArgCommand(LPCSTR lpszCommandVerb, LPSTR lpszMboxName,
|
|
IMAP_COMMAND icCommandID,
|
|
WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
HRESULT hrResult;
|
|
char szBuffer[CMDLINE_BUFSIZE];
|
|
CIMAPCmdInfo *piciCommand;
|
|
LPSTR p, pszUTF7MboxName;
|
|
|
|
// Verify proper server state and set us up as current command
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszCommandVerb);
|
|
Assert(NULL != lpszMboxName);
|
|
Assert(icNO_COMMAND != icCommandID);
|
|
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less (always TRUE in this case)
|
|
if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
// Initialize variables
|
|
pszUTF7MboxName = NULL;
|
|
|
|
piciCommand = new CIMAPCmdInfo(this, icCommandID, ssAuthenticated,
|
|
wParam, lParam, pCBHandler);
|
|
|
|
// Construct command line
|
|
p = szBuffer;
|
|
p += wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s", piciCommand->szTag, lpszCommandVerb);
|
|
|
|
// Convert mailbox name to UTF-7
|
|
hrResult = _MultiByteToModifiedUTF7(lpszMboxName, &pszUTF7MboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Don't worry about long mailbox name overflow, long mbox names will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szBuffer, &p, sizeof(szBuffer), pszUTF7MboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Send command
|
|
p += wnsprintf(p, ARRAYSIZE(szBuffer) - (DWORD)(p - szBuffer), "\r\n"); // Append CRLF
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, (DWORD) (p - szBuffer));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
if (NULL != pszUTF7MboxName)
|
|
MemFree(pszUTF7MboxName);
|
|
|
|
return hrResult;
|
|
} // OneArgCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function TwoArgCommand
|
|
//
|
|
// Purpose:
|
|
// This function can construct a command line for any function of the
|
|
// form: <tag> <command> <astring> <astring>.
|
|
//
|
|
// This function constructs the command line, sends it out, and returns the
|
|
// result of the send operation.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
|
|
// LPCSTR lpszFirstArg [in] - a C string representing the first argument for
|
|
// the command. It is automatically converted to an IMAP astring.
|
|
// LPCSTR lpszSecondArg [in] - a C string representing the first argument
|
|
// for the command. It is automatically converted to an IMAP astring.
|
|
// IMAP_COMMAND icCommandID [in] - the command ID for this command,
|
|
// eg, icCREATE_COMMAND.
|
|
// SERVERSTATE ssMinimumState [in] - minimum server state required for
|
|
// the given command. Used for debug purposes only.
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::TwoArgCommand(LPCSTR lpszCommandVerb,
|
|
LPCSTR lpszFirstArg,
|
|
LPCSTR lpszSecondArg,
|
|
IMAP_COMMAND icCommandID,
|
|
SERVERSTATE ssMinimumState,
|
|
WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
HRESULT hrResult;
|
|
CIMAPCmdInfo *piciCommand;
|
|
char szCommandLine[CMDLINE_BUFSIZE];
|
|
LPSTR p;
|
|
|
|
// Verify proper server state and set us up as current command
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszCommandVerb);
|
|
Assert(NULL != lpszFirstArg);
|
|
Assert(NULL != lpszSecondArg);
|
|
Assert(icNO_COMMAND != icCommandID);
|
|
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less
|
|
if (ssMinimumState > m_ssServerState &&
|
|
(ssConnecting != m_ssServerState || ssMinimumState > ssAuthenticated)) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
piciCommand = new CIMAPCmdInfo(this, icCommandID, ssMinimumState,
|
|
wParam, lParam, pCBHandler);
|
|
if (piciCommand == NULL) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
// Send command to server, wait for response
|
|
p = szCommandLine;
|
|
p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s %s", piciCommand->szTag, lpszCommandVerb);
|
|
|
|
// Don't worry about buffer overflow, long strings will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
|
|
sizeof(szCommandLine), lpszFirstArg);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Don't worry about buffer overflow, long strings will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
|
|
sizeof(szCommandLine), lpszSecondArg);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), "\r\n"); // Append CRLF
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
return hrResult;
|
|
} // TwoArgCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: RangedCommand
|
|
//
|
|
// Purpose:
|
|
// This function can construct a command line for any function of the
|
|
// form: <tag> <command> <msg range> <string>.
|
|
//
|
|
// This function constructs the command line, sends it out, and returns the
|
|
// result of the send operation. It is the caller's responsiblity to construct
|
|
// a string with proper IMAP syntax.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR lpszCommandVerb [in] - the command verb, eg, "SEARCH".
|
|
// boolean bUIDPrefix [in] - TRUE if the command verb should be prefixed with
|
|
// UID, as in the case of "UID SEARCH".
|
|
// IRangeList *pMsgRange [in] - the message range for this command. The
|
|
// caller can pass NULL for this argument to omit the range, but ONLY
|
|
// if the pMsgRange represents a UID message range.
|
|
// boolean bUIDRangeList [in] - TRUE if pMsgRange represents a UID range,
|
|
// FALSE if pMsgRange represents a message sequence number range. Ignored
|
|
// if pMsgRange is NULL.
|
|
// boolean bAStringCmdArgs [in] - TRUE if lpszCmdArgs should be sent as
|
|
// an ASTRING, FALSE if lpszCmdArgs should be sent
|
|
// LPCSTR lpszCmdArgs [in] - a C string representing the remaining argument
|
|
// for the command. It is the caller's responsibility to ensure that
|
|
// this string is proper IMAP syntax.
|
|
// IMAP_COMMAND icCommandID [in] - the command ID for this command,
|
|
// eg, icSEARCH_COMMAND.
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::RangedCommand(LPCSTR lpszCommandVerb,
|
|
boolean bUIDPrefix,
|
|
IRangeList *pMsgRange,
|
|
boolean bUIDRangeList,
|
|
boolean bAStringCmdArgs,
|
|
LPSTR lpszCmdArgs,
|
|
IMAP_COMMAND icCommandID,
|
|
WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
HRESULT hrResult;
|
|
CIMAPCmdInfo *piciCommand;
|
|
char szCommandLine[CMDLINE_BUFSIZE];
|
|
DWORD dwCmdLineLen;
|
|
BOOL fAStringPrependSpace = TRUE;
|
|
|
|
// Verify proper server state and set us up as current command
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszCommandVerb);
|
|
Assert(NULL != lpszCmdArgs);
|
|
AssertSz(NULL != pMsgRange || TRUE == bUIDRangeList ||
|
|
icSEARCH_COMMAND == icCommandID, "Only UID cmds or SEARCH can omit msg range");
|
|
Assert(icNO_COMMAND != icCommandID);
|
|
|
|
// All ranged commands require selected state
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less (always FALSE in this case)
|
|
if (ssSelected > m_ssServerState) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
piciCommand = new CIMAPCmdInfo(this, icCommandID, ssSelected,
|
|
wParam, lParam, pCBHandler);
|
|
if (NULL == piciCommand) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
piciCommand->fUIDRangeList = bUIDRangeList;
|
|
|
|
// Construct command tag and verb, append to command-line queue
|
|
dwCmdLineLen = wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine),
|
|
bUIDPrefix ? "%s UID %s " : "%s %s ",
|
|
piciCommand->szTag, lpszCommandVerb);
|
|
|
|
// Special case: if SEARCH command, UID rangelist requires "UID" in front of range
|
|
if (icSEARCH_COMMAND == icCommandID && NULL != pMsgRange && bUIDRangeList)
|
|
dwCmdLineLen += wnsprintf(szCommandLine + dwCmdLineLen, (ARRAYSIZE(szCommandLine) - dwCmdLineLen), "UID ");
|
|
|
|
if (NULL != pMsgRange) {
|
|
Assert(dwCmdLineLen + 1 < sizeof(szCommandLine)); // Check for overflow
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, dwCmdLineLen);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Add message range to command-line queue, if it exists
|
|
hrResult = SendRangelist(piciCommand, pMsgRange, bUIDRangeList);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
pMsgRange = NULL; // If we get to this point, MemFree of rangelist will be handled
|
|
fAStringPrependSpace = FALSE; // Rangelist automatically APPENDS space
|
|
dwCmdLineLen = 0;
|
|
}
|
|
|
|
// Now append the command-line arguments (remember to append CRLF)
|
|
if (bAStringCmdArgs) {
|
|
LPSTR p;
|
|
|
|
p = szCommandLine + dwCmdLineLen;
|
|
// Don't worry about long mailbox name overflow, long mbox names will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
|
|
sizeof(szCommandLine), lpszCmdArgs, fAStringPrependSpace);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), "\r\n"); // Append CRLF
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END,
|
|
szCommandLine, (DWORD) (p - szCommandLine));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
}
|
|
else {
|
|
if (dwCmdLineLen > 0) {
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, dwCmdLineLen);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
}
|
|
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END | sclAPPEND_CRLF,
|
|
lpszCmdArgs, lstrlen(lpszCmdArgs));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
}
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (FAILED(hrResult)) {
|
|
if (NULL != piciCommand)
|
|
delete piciCommand;
|
|
}
|
|
|
|
return hrResult;
|
|
} // RangedCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function TwoMailboxCommand
|
|
//
|
|
// Purpose:
|
|
// This function is a wrapper function for TwoArgCommand. This function
|
|
// converts the two mailbox names to modified IMAP UTF-7 before submitting
|
|
// the two arguments to TwoArgCommand.
|
|
//
|
|
// Arguments:
|
|
// Same as for TwoArgCommandm except for name/const change:
|
|
// LPSTR lpszFirstMbox [in] - pointer to the first mailbox argument.
|
|
// LPSTR lpszSecondMbox [in] - pointer to the second mailbox argument.
|
|
//
|
|
// Returns:
|
|
// Same as TwoArgCommand.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::TwoMailboxCommand(LPCSTR lpszCommandVerb,
|
|
LPSTR lpszFirstMbox,
|
|
LPSTR lpszSecondMbox,
|
|
IMAP_COMMAND icCommandID,
|
|
SERVERSTATE ssMinimumState,
|
|
WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
LPSTR pszUTF7FirstMbox, pszUTF7SecondMbox;
|
|
HRESULT hrResult;
|
|
|
|
// Initialize variables
|
|
pszUTF7FirstMbox = NULL;
|
|
pszUTF7SecondMbox = NULL;
|
|
|
|
// Convert both mailbox names to UTF-7
|
|
hrResult = _MultiByteToModifiedUTF7(lpszFirstMbox, &pszUTF7FirstMbox);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = _MultiByteToModifiedUTF7(lpszSecondMbox, &pszUTF7SecondMbox);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = TwoArgCommand(lpszCommandVerb, pszUTF7FirstMbox, pszUTF7SecondMbox,
|
|
icCommandID, ssMinimumState, wParam, lParam, pCBHandler);
|
|
|
|
exit:
|
|
if (NULL != pszUTF7FirstMbox)
|
|
MemFree(pszUTF7FirstMbox);
|
|
|
|
if (NULL != pszUTF7SecondMbox)
|
|
MemFree(pszUTF7SecondMbox);
|
|
|
|
return hrResult;
|
|
} // TwoMailboxCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: parseCapability
|
|
//
|
|
// Purpose:
|
|
// The CAPABILITY response from the IMAP server consists of a list of
|
|
// capbilities, with each capability names separated by a space. This
|
|
// function takes a capability name (null-terminated) as its argument.
|
|
// If the name is recognized, we set the appropriate flags in
|
|
// m_dwCapabilityFlags. Otherwise, we do nothing.
|
|
//
|
|
//
|
|
// Returns:
|
|
// No return value. This function always succeeds.
|
|
//***************************************************************************
|
|
void CImap4Agent::parseCapability (LPSTR lpszCapabilityToken)
|
|
{
|
|
DWORD dwCapabilityFlag;
|
|
LPSTR p;
|
|
int iResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
p = lpszCapabilityToken;
|
|
dwCapabilityFlag = 0;
|
|
switch (*lpszCapabilityToken) {
|
|
case 'I':
|
|
case 'i': // Possible IMAP4, IMAP4rev1
|
|
iResult = lstrcmpi(p, "IMAP4");
|
|
if (0 == iResult) {
|
|
dwCapabilityFlag = IMAP_CAPABILITY_IMAP4;
|
|
break;
|
|
}
|
|
|
|
iResult = lstrcmpi(p, "IMAP4rev1");
|
|
if (0 == iResult) {
|
|
dwCapabilityFlag = IMAP_CAPABILITY_IMAP4rev1;
|
|
break;
|
|
}
|
|
|
|
iResult = lstrcmpi(p, "IDLE");
|
|
if (0 == iResult) {
|
|
dwCapabilityFlag = IMAP_CAPABILITY_IDLE;
|
|
break;
|
|
}
|
|
|
|
break; // case 'I' for possible IMAP4, IMAP4rev1, IDLE
|
|
|
|
case 'A':
|
|
case 'a': // Possible AUTH
|
|
if (0 == StrCmpNI(p, "AUTH-", 5) ||
|
|
0 == StrCmpNI(p, "AUTH=", 5)) {
|
|
// Parse the authentication mechanism after the '-' or '='
|
|
// I don't recognize any, at the moment
|
|
p += 5;
|
|
AddAuthMechanism(p);
|
|
}
|
|
|
|
break; // case 'A' for possible AUTH
|
|
|
|
default:
|
|
break; // Do nothing
|
|
} // switch (*lpszCapabilityToken)
|
|
|
|
m_dwCapabilityFlags |= dwCapabilityFlag;
|
|
} // parseCapability
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AddAuthMechanism
|
|
//
|
|
// Purpose:
|
|
// This function adds an authentication token from the server (returned via
|
|
// CAPABILITY) to our internal list of authentication mechanisms supported
|
|
// by the server.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszAuthMechanism [in] - a pointer to a null-terminated
|
|
// authentication token from the server returned via CAPABILITY. For
|
|
// instance, "KERBEROS_V4" is an example of an auth token.
|
|
//***************************************************************************
|
|
void CImap4Agent::AddAuthMechanism(LPSTR pszAuthMechanism)
|
|
{
|
|
AssertSz(NULL == m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens],
|
|
"Memory's a-leaking, and you've just lost an authentication mechanism.");
|
|
|
|
if (NULL == pszAuthMechanism || '\0' == *pszAuthMechanism) {
|
|
AssertSz(FALSE, "No authentication mechanism, here!");
|
|
return;
|
|
}
|
|
|
|
if (m_asAuthStatus.iNumAuthTokens >= MAX_AUTH_TOKENS) {
|
|
AssertSz(FALSE, "No room in array for more auth tokens!");
|
|
return;
|
|
}
|
|
|
|
m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens] =
|
|
PszDupA(pszAuthMechanism);
|
|
Assert(NULL != m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens]);
|
|
|
|
m_asAuthStatus.iNumAuthTokens += 1;
|
|
} // AddAuthMechanism
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Capability
|
|
//
|
|
// Purpose:
|
|
// The CImap4Agent class always asks for the server's CAPABILITIES after
|
|
// a connection is established. The result is saved in a register and
|
|
// is available by calling this function.
|
|
//
|
|
// Arguments:
|
|
// DWORD *pdwCapabilityFlags [out] - a DWORD with bit-flags specifying
|
|
// which capabilities this IMAP server supports is returned here.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Capability (DWORD *pdwCapabilityFlags)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pdwCapabilityFlags);
|
|
|
|
if (m_ssServerState < ssNonAuthenticated) {
|
|
AssertSz(FALSE, "Must be connected before I can return capabilities");
|
|
*pdwCapabilityFlags = 0;
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
else {
|
|
*pdwCapabilityFlags = m_dwCapabilityFlags;
|
|
return S_OK;
|
|
}
|
|
} // Capability
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Select
|
|
//
|
|
// Purpose:
|
|
// This function issues a SELECT command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - pointer to IMAP-compliant mailbox name
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Select(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("SELECT", lpszMailboxName, icSELECT_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
// Successful SELECT bumps server status to ssSelected
|
|
} // Select
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Examine
|
|
//
|
|
// Purpose:
|
|
// This function issues an EXAMINE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// Same as for the Select() function.
|
|
//
|
|
// Returns:
|
|
// Same as for the Select() function.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Examine(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("EXAMINE", lpszMailboxName, icEXAMINE_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
// Successful EXAMINE bumps server status to ssSelected
|
|
} // Examine
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Create
|
|
//
|
|
// Purpose:
|
|
// This function issues a CREATE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Create(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("CREATE", lpszMailboxName, icCREATE_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
} // Create
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Delete
|
|
//
|
|
// Purpose:
|
|
// This function issues a DELETE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Delete(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("DELETE", lpszMailboxName, icDELETE_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
} // Delete
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Rename
|
|
//
|
|
// Purpose:
|
|
// This function issues a RENAME command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - CURRENT IMAP-compliant name of the mailbox.
|
|
// LPSTR lpszNewMailboxName - NEW IMAP-compliant name of the mailbox.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Rename(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName,
|
|
LPSTR lpszNewMailboxName)
|
|
{
|
|
return TwoMailboxCommand("RENAME", lpszMailboxName, lpszNewMailboxName,
|
|
icRENAME_COMMAND, ssAuthenticated, wParam, lParam, pCBHandler);
|
|
} // Rename
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Subscribe
|
|
//
|
|
// Purpose:
|
|
// This function issues a SUBSCRIBE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Subscribe(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("SUBSCRIBE", lpszMailboxName, icSUBSCRIBE_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
} // Subscribe
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Unsubscribe
|
|
//
|
|
// Purpose:
|
|
// This function issues an UNSUBSCRIBE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Unsubscribe(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
return OneArgCommand("UNSUBSCRIBE", lpszMailboxName, icUNSUBSCRIBE_COMMAND,
|
|
wParam, lParam, pCBHandler);
|
|
} // Unsubscribe
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: List
|
|
//
|
|
// Purpose:
|
|
// This function issues a LIST command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxNameReference - IMAP-compliant reference for mbox name
|
|
// LPSTR lpszMailboxNamePattern - IMAP-compliant pattern for mailbox name
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::List(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxNameReference,
|
|
LPSTR lpszMailboxNamePattern)
|
|
{
|
|
return TwoMailboxCommand("LIST", lpszMailboxNameReference,
|
|
lpszMailboxNamePattern, icLIST_COMMAND, ssAuthenticated, wParam,
|
|
lParam, pCBHandler);
|
|
} // List
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Lsub
|
|
//
|
|
// Purpose:
|
|
// This function issues a LSUB command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxNameReference - IMAP-compliant reference for mbox name
|
|
// LPSTR lpszMailboxNamePattern - IMAP-compliant pattern for mailbox name.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Lsub(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxNameReference,
|
|
LPSTR lpszMailboxNamePattern)
|
|
{
|
|
return TwoMailboxCommand("LSUB", lpszMailboxNameReference,
|
|
lpszMailboxNamePattern, icLSUB_COMMAND, ssAuthenticated, wParam,
|
|
lParam, pCBHandler);
|
|
} // Lsub
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Append
|
|
//
|
|
// Purpose:
|
|
// This function issues an APPEND command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszMailboxName - IMAP-compliant mailbox name to append message to.
|
|
// LPSTR lpszMessageFlags - IMAP-compliant list of msg flags to set for msg.
|
|
// Set to NULL to set no message flags. (Avoid passing "()" due to old Cyrus
|
|
// server bug). $REVIEW: This should be changed to IMAP_MSGFLAGS!!!
|
|
// FILETIME ftMessageDateTime - date/time to associate with msg (GMT/UTC)
|
|
// LPSTREAM lpstmMessageToSave - the message to save, in RFC822 format.
|
|
// No need to rewind the stream, this is done by CConnection::SendStream.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Append(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszMailboxName,
|
|
LPSTR lpszMessageFlags,
|
|
FILETIME ftMessageDateTime,
|
|
LPSTREAM lpstmMessageToSave)
|
|
{
|
|
// PARTYTIME!!
|
|
const SYSTEMTIME stDefaultDateTime = {1999, 12, 5, 31, 23, 59, 59, 999};
|
|
|
|
HRESULT hrResult;
|
|
FILETIME ftLocalTime;
|
|
SYSTEMTIME stMsgDateTime;
|
|
DWORD dwTimeZoneId;
|
|
LONG lTZBias, lTZHour, lTZMinute;
|
|
TIME_ZONE_INFORMATION tzi;
|
|
ULONG ulMessageSize;
|
|
char szCommandLine[CMDLINE_BUFSIZE];
|
|
CIMAPCmdInfo *piciCommand;
|
|
LPSTR p, pszUTF7MailboxName;
|
|
|
|
char cTZSign;
|
|
BOOL bResult;
|
|
|
|
// Check arguments
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszMailboxName);
|
|
Assert(NULL != lpstmMessageToSave);
|
|
|
|
// Verify proper server state
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less (always TRUE in this case)
|
|
if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
// Initialize variables
|
|
pszUTF7MailboxName = NULL;
|
|
piciCommand = new CIMAPCmdInfo(this, icAPPEND_COMMAND, ssAuthenticated,
|
|
wParam, lParam, pCBHandler);
|
|
if (piciCommand == NULL) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
// Convert FILETIME to IMAP date/time spec
|
|
bResult = FileTimeToLocalFileTime(&ftMessageDateTime, &ftLocalTime);
|
|
if (bResult)
|
|
bResult = FileTimeToSystemTime(&ftLocalTime, &stMsgDateTime);
|
|
|
|
if (FALSE == bResult) {
|
|
Assert(FALSE); // Conversion failed
|
|
// If retail version, just substitute a default system time
|
|
stMsgDateTime = stDefaultDateTime;
|
|
}
|
|
|
|
// Figure out time zone (stolen from MsgOut.cpp's HrEmitDateTime)
|
|
dwTimeZoneId = GetTimeZoneInformation (&tzi);
|
|
switch (dwTimeZoneId)
|
|
{
|
|
case TIME_ZONE_ID_STANDARD:
|
|
lTZBias = tzi.Bias + tzi.StandardBias;
|
|
break;
|
|
|
|
case TIME_ZONE_ID_DAYLIGHT:
|
|
lTZBias = tzi.Bias + tzi.DaylightBias;
|
|
break;
|
|
|
|
case TIME_ZONE_ID_UNKNOWN:
|
|
default:
|
|
lTZBias = 0 ; // $$BUG: what's supposed to happen here?
|
|
break;
|
|
}
|
|
|
|
lTZHour = lTZBias / 60;
|
|
lTZMinute = lTZBias % 60;
|
|
cTZSign = (lTZHour < 0) ? '+' : '-';
|
|
|
|
// Get size of message
|
|
hrResult = HrGetStreamSize(lpstmMessageToSave, &ulMessageSize);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Send command to server
|
|
// Format: tag APPEND mboxName msgFlags "dd-mmm-yyyy hh:mm:ss +/-hhmm" {msgSize}
|
|
p = szCommandLine;
|
|
p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s APPEND", piciCommand->szTag);
|
|
|
|
// Convert mailbox name to modified UTF-7
|
|
hrResult = _MultiByteToModifiedUTF7(lpszMailboxName, &pszUTF7MailboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Don't worry about long mailbox name overflow, long mbox names will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szCommandLine, &p, sizeof(szCommandLine),
|
|
pszUTF7MailboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
if (NULL != lpszMessageFlags)
|
|
p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), " %.250s", lpszMessageFlags);
|
|
|
|
// Limited potential for buffer overflow: mailbox names over 128 bytes are sent as
|
|
// literals, so worst case buffer usage is 11+128+34+flags+literalNum=173+~20=~200
|
|
p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine),
|
|
" \"%2d-%.3s-%04d %02d:%02d:%02d %c%02d%02d\" {%lu}\r\n",
|
|
stMsgDateTime.wDay, lpszMonthsOfTheYear[stMsgDateTime.wMonth],
|
|
stMsgDateTime.wYear, stMsgDateTime.wHour, stMsgDateTime.wMinute,
|
|
stMsgDateTime.wSecond,
|
|
cTZSign, abs(lTZHour), abs(lTZMinute),
|
|
ulMessageSize);
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Don't have to wait for response... message body will be queued
|
|
hrResult = SendLiteral(piciCommand, lpstmMessageToSave, ulMessageSize);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Have to send CRLF to end command line (the literal's CRLF doesn't count)
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, c_szCRLF, lstrlen(c_szCRLF));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
if (NULL != pszUTF7MailboxName)
|
|
MemFree(pszUTF7MailboxName);
|
|
|
|
return hrResult;
|
|
} // Append
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Close
|
|
//
|
|
// Purpose:
|
|
// This function issues a CLOSE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Close (WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
return NoArgCommand("CLOSE", icCLOSE_COMMAND, ssSelected,
|
|
wParam, lParam, pCBHandler);
|
|
} // Close
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Expunge
|
|
//
|
|
// Purpose:
|
|
// This function issues an EXPUNGE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure on send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Expunge (WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
return NoArgCommand("EXPUNGE", icEXPUNGE_COMMAND, ssSelected,
|
|
wParam, lParam, pCBHandler);
|
|
} // Expunge
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Search
|
|
//
|
|
// Purpose:
|
|
// This function issues a SEARCH command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR lpszSearchCriteria - IMAP-compliant list of search criteria
|
|
// boolean bReturnUIDs - if TRUE, we prepend "UID" to command.
|
|
// IRangeList *pMsgRange [in] - range of messages over which to operate
|
|
// the search. This argument should be NULL to exclude the message
|
|
// set from the search criteria.
|
|
// boolean bUIDRangeList [in] - TRUE if pMsgRange refers to a UID range,
|
|
// FALSE if pMsgRange refers to a message sequence number range. If
|
|
// pMsgRange is NULL, this argument is ignored.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure on send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Search(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR lpszSearchCriteria,
|
|
boolean bReturnUIDs, IRangeList *pMsgRange,
|
|
boolean bUIDRangeList)
|
|
{
|
|
return RangedCommand("SEARCH", bReturnUIDs, pMsgRange, bUIDRangeList,
|
|
rcNOT_ASTRING_ARG, lpszSearchCriteria, icSEARCH_COMMAND, wParam,
|
|
lParam, pCBHandler);
|
|
} // Search
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Fetch
|
|
//
|
|
// Purpose:
|
|
// This function issues a FETCH command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// IRangeList *pMsgRange [in] - range of messages to fetch. The caller
|
|
// should pass NULL if he is using UIDs and he wants to generate his
|
|
// own message set (in lpszFetchArgs). If the caller is using msg
|
|
// seq nums, this argument MUST be specified to allow this class to
|
|
// resequence the msg nums as required.
|
|
// boolean bUIDMsgRange [in] - if TRUE, prepends "UID" to FETCH command and
|
|
// treats pMsgRange as a UID range.
|
|
// LPSTR lpszFetchArgs - arguments to the FETCH command
|
|
//
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of the send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Fetch(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
IRangeList *pMsgRange,
|
|
boolean bUIDMsgRange,
|
|
LPSTR lpszFetchArgs)
|
|
{
|
|
return RangedCommand("FETCH", bUIDMsgRange, pMsgRange, bUIDMsgRange,
|
|
rcNOT_ASTRING_ARG, lpszFetchArgs, icFETCH_COMMAND, wParam, lParam,
|
|
pCBHandler);
|
|
} // Fetch
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Store
|
|
//
|
|
// Purpose:
|
|
// This function issues a STORE command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// IRangeList *pMsgRange [in] - range of messages to store. The caller
|
|
// should pass NULL if he is using UIDs and he wants to generate his
|
|
// own message set (in lpszStoreArgs). If the caller is using msg
|
|
// seq nums, this argument MUST be specified to allow this class to
|
|
// resequence the msg nums as required.
|
|
// boolean bUIDRangeList [in] - if TRUE, we prepend "UID" to the STORE command
|
|
// LPSTR lpszStoreArgs - arguments for the STORE command.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of the send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Store(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
IRangeList *pMsgRange,
|
|
boolean bUIDRangeList,
|
|
LPSTR lpszStoreArgs)
|
|
{
|
|
return RangedCommand("STORE", bUIDRangeList, pMsgRange, bUIDRangeList,
|
|
rcNOT_ASTRING_ARG, lpszStoreArgs, icSTORE_COMMAND, wParam, lParam,
|
|
pCBHandler);
|
|
} // Store
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Copy
|
|
//
|
|
// Purpose:
|
|
// This function issues a COPY command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// IRangeList *pMsgRange [in] - the range of messages to copy. This
|
|
// argument must be supplied.
|
|
// boolean bUIDRangeList [in] - if TRUE, prepends "UID" to COPY command
|
|
// LPSTR lpszMailboxName [in] - C String of mailbox name
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Copy(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
IRangeList *pMsgRange,
|
|
boolean bUIDRangeList,
|
|
LPSTR lpszMailboxName)
|
|
{
|
|
HRESULT hrResult;
|
|
LPSTR pszUTF7MailboxName;
|
|
DWORD dwNumCharsWritten;
|
|
|
|
// Initialize variables
|
|
pszUTF7MailboxName = NULL;
|
|
|
|
// Convert the mailbox name to modified UTF-7
|
|
hrResult = _MultiByteToModifiedUTF7(lpszMailboxName, &pszUTF7MailboxName);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = RangedCommand("COPY", bUIDRangeList, pMsgRange, bUIDRangeList,
|
|
rcASTRING_ARG, pszUTF7MailboxName, icCOPY_COMMAND, wParam, lParam,
|
|
pCBHandler);
|
|
|
|
exit:
|
|
if (NULL != pszUTF7MailboxName)
|
|
MemFree(pszUTF7MailboxName);
|
|
|
|
return hrResult;
|
|
} // Copy
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Status
|
|
//
|
|
// Purpose:
|
|
// This function issues a STATUS command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
// LPSTR pszMailboxName [in] - the mailbox which you want to get the
|
|
// STATUS of.
|
|
// LPSTR pszStatusCmdArgs [in] - the arguments for the STATUS command,
|
|
// eg, "(MESSAGES UNSEEN)".
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Status(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler,
|
|
LPSTR pszMailboxName,
|
|
LPSTR pszStatusCmdArgs)
|
|
{
|
|
HRESULT hrResult;
|
|
CIMAPCmdInfo *piciCommand;
|
|
char szCommandLine[CMDLINE_BUFSIZE];
|
|
LPSTR p, pszUTF7MailboxName;
|
|
|
|
// Verify proper server state and set us up as current command
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszMailboxName);
|
|
Assert(NULL != pszStatusCmdArgs);
|
|
|
|
// Initialize variables
|
|
pszUTF7MailboxName = NULL;
|
|
|
|
// Verify proper server state
|
|
// Only accept cmds if server is in proper state, OR if we're connecting,
|
|
// and the cmd requires Authenticated state or less (always TRUE in this case)
|
|
if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
|
|
AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
|
|
return IXP_E_IMAP_IMPROPER_SVRSTATE;
|
|
}
|
|
|
|
piciCommand = new CIMAPCmdInfo(this, icSTATUS_COMMAND, ssAuthenticated,
|
|
wParam, lParam, pCBHandler);
|
|
if (piciCommand == NULL) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
// Send STATUS command to server, wait for response
|
|
p = szCommandLine;
|
|
p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s %s", piciCommand->szTag, "STATUS");
|
|
|
|
// Convert the mailbox name to modified UTF-7
|
|
hrResult = _MultiByteToModifiedUTF7(pszMailboxName, &pszUTF7MailboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Don't worry about long mailbox name overflow, long mbox names will be sent as literals
|
|
hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
|
|
sizeof(szCommandLine), pszUTF7MailboxName);
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Limited overflow risk: since literal threshold is 128, max buffer usage is
|
|
// 11+128+2+args = 141+~20 = 161
|
|
p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), " %.300s\r\n", pszStatusCmdArgs);
|
|
hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
|
|
if (FAILED(hrResult))
|
|
goto error;
|
|
|
|
// Transmit command and register with IMAP response parser
|
|
hrResult = SubmitIMAPCommand(piciCommand);
|
|
|
|
error:
|
|
if (NULL != pszUTF7MailboxName)
|
|
MemFree(pszUTF7MailboxName);
|
|
|
|
if (FAILED(hrResult))
|
|
delete piciCommand;
|
|
|
|
return hrResult;
|
|
} // Status
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: Noop
|
|
//
|
|
// Purpose:
|
|
// This function issues a NOOP command to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// WPARAM wParam [in] - (see below)
|
|
// LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
|
|
// the caller to this IMAP command and its responses. Can be anything,
|
|
// but note that the value of 0, 0 is reserved for unsolicited responses.
|
|
// IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
|
|
// responses for this command. If this is NULL, the default CB handler
|
|
// is used.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure of send operation.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::Noop(WPARAM wParam, LPARAM lParam,
|
|
IIMAPCallback *pCBHandler)
|
|
{
|
|
return NoArgCommand("NOOP", icNOOP_COMMAND, ssNonAuthenticated,
|
|
wParam, lParam, pCBHandler);
|
|
} // Noop
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: EnterIdleMode
|
|
//
|
|
// Purpose:
|
|
// This function issues the IDLE command to the server, if the server
|
|
// supports this extension. It should be called when no commands are
|
|
// currently begin transmitted (or waiting to be transmitted) and no
|
|
// commands are expected back from the server. When the next IMAP command is
|
|
// issued, the send machine automatically leaves IDLE mode before issuing
|
|
// the IMAP command.
|
|
//***************************************************************************
|
|
void CImap4Agent::EnterIdleMode(void)
|
|
{
|
|
CIMAPCmdInfo *piciIdleCmd;
|
|
HRESULT hrResult;
|
|
char sz[NUM_TAG_CHARS + 7 + 1];
|
|
int i;
|
|
|
|
// Check if this server supports IDLE
|
|
if (0 == (m_dwCapabilityFlags & IMAP_CAPABILITY_IDLE))
|
|
return; // Nothing to do here
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
piciIdleCmd = NULL;
|
|
|
|
piciIdleCmd = new CIMAPCmdInfo(this, icIDLE_COMMAND, ssAuthenticated,
|
|
0, 0, NULL);
|
|
if (NULL == piciIdleCmd) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
i = wnsprintf(sz, ARRAYSIZE(sz), "%.4s IDLE\r\n", piciIdleCmd->szTag);
|
|
Assert(11 == i);
|
|
hrResult = SendCmdLine(piciIdleCmd, sclAPPEND_TO_END, sz, i);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = SendPause(piciIdleCmd);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = SendCmdLine(piciIdleCmd, sclAPPEND_TO_END, c_szDONE,
|
|
sizeof(c_szDONE) - 1);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = SendStop(piciIdleCmd);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
hrResult = SubmitIMAPCommand(piciIdleCmd);
|
|
|
|
exit:
|
|
if (FAILED(hrResult)) {
|
|
AssertSz(FALSE, "EnterIdleMode failure");
|
|
if (NULL != piciIdleCmd)
|
|
delete piciIdleCmd;
|
|
}
|
|
} // EnterIdleMode
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GenerateMsgSet
|
|
//
|
|
// Purpose:
|
|
// This function takes an array of message ID's (could be UIDs or Msg
|
|
// sequence numbers, this function doesn't care) and converts it to an IMAP
|
|
// set (see Formal Syntax in RFC1730). If the given array of message ID's is
|
|
// SORTED, this function can coalesce a run of numbers into a range. For
|
|
// unsorted arrays, it doesn't bother coalescing the numbers.
|
|
//
|
|
// Arguments:
|
|
// LPSTR lpszDestination [out] - output buffer for IMAP set. NOTE that the
|
|
// output string deposited here has a leading comma which must be
|
|
// DWORD dwSizeOfDestination [in] - size of output buffer.
|
|
// DWORD *pMsgID [in] - pointer to an array of message ID's (UIDs or Msg
|
|
// sequence numbers)
|
|
// DWORD cMsgID [in] - the number of message ID's passed in the *pMsgID
|
|
// array.
|
|
//
|
|
// Returns:
|
|
// DWORD indicating the number of characters written. Adding this value
|
|
// to the value of lpszDestination will point to the null-terminator at
|
|
// the end of the output string.
|
|
//***************************************************************************
|
|
DWORD CImap4Agent::GenerateMsgSet(LPSTR lpszDestination,
|
|
DWORD dwSizeOfDestination,
|
|
DWORD *pMsgID,
|
|
DWORD cMsgID)
|
|
{
|
|
LPSTR p;
|
|
DWORD dwNumMsgsCopied, idStartOfRange, idEndOfRange;
|
|
DWORD dwNumMsgsInRun; // Used to detect if we are in a run of consecutive nums
|
|
boolean bFirstRange; // TRUE if outputting first msg range in set
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != lpszDestination);
|
|
Assert(0 != dwSizeOfDestination);
|
|
Assert(NULL != pMsgID);
|
|
Assert(0 != cMsgID);
|
|
|
|
// Construct the set of messages to copy
|
|
p = lpszDestination;
|
|
DWORD cchSize = dwSizeOfDestination;
|
|
idStartOfRange = *pMsgID;
|
|
dwNumMsgsInRun = 0;
|
|
bFirstRange = TRUE; // Suppress leading comma for first message range in set
|
|
for (dwNumMsgsCopied = 0; dwNumMsgsCopied < cMsgID; dwNumMsgsCopied++ ) {
|
|
if (*pMsgID == idStartOfRange + dwNumMsgsInRun) {
|
|
idEndOfRange = *pMsgID; // Construct a range out of consecutive numbers
|
|
dwNumMsgsInRun += 1;
|
|
}
|
|
else {
|
|
// No more consecutive numbers found, output the range
|
|
cchSize = (DWORD)(dwSizeOfDestination - (p - lpszDestination));
|
|
AppendMsgRange(&p, cchSize, idStartOfRange, idEndOfRange, bFirstRange);
|
|
idStartOfRange = *pMsgID;
|
|
idEndOfRange = *pMsgID;
|
|
dwNumMsgsInRun = 1;
|
|
bFirstRange = FALSE; // Turn on leading comma from this point on
|
|
}
|
|
pMsgID += 1;
|
|
} // for
|
|
|
|
if (dwNumMsgsInRun > 0)
|
|
{
|
|
// Perform append for last msgID
|
|
cchSize = (dwSizeOfDestination - (DWORD)(p - lpszDestination));
|
|
AppendMsgRange(&p, cchSize, idStartOfRange, idEndOfRange, bFirstRange);
|
|
}
|
|
|
|
// Check for buffer overflow
|
|
Assert(p < lpszDestination + dwSizeOfDestination);
|
|
|
|
return (DWORD) (p - lpszDestination);
|
|
} // GenerateMsgSet
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AppendMsgRange
|
|
//
|
|
// Purpose:
|
|
// This function appends a single message range to the given string
|
|
// pointer, either in the form ",seq num" or ",seq num:seq num" (NOTE the
|
|
// leading comma: this should be suppressed for the first message range in
|
|
// the set by setting bSuppressComma to TRUE).
|
|
//
|
|
// Arguments:
|
|
// LPSTR *ppDest [in/out] - this pointer should always point to the end
|
|
// of the string currently being constructed, although there need not be
|
|
// a null-terminator present. After this function appends its message
|
|
// range to the string, it advances *ppDest by the correct amount.
|
|
// Note that it is the caller's responsibility to perform bounds checking.
|
|
// const DWORD idStartOfRange [in] - the first msg number of the msg range.
|
|
// const DWORD idEndOfRange [in] - the last msg number of the msg range.
|
|
// const boolean bSuppressComma [in] - TRUE if the leading comma should be
|
|
// suppressed. This is generally TRUE only for the first message range
|
|
// in the set.
|
|
//
|
|
// Returns:
|
|
// Nothing. Given valid arguments, this function cannot fail.
|
|
//***************************************************************************
|
|
void CImap4Agent::AppendMsgRange(LPSTR *ppDest, const DWORD cchSizeDest, const DWORD idStartOfRange,
|
|
const DWORD idEndOfRange, boolean bSuppressComma)
|
|
{
|
|
LPSTR lpszComma;
|
|
int numCharsWritten;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppDest);
|
|
Assert(NULL != *ppDest);
|
|
Assert(0 != idStartOfRange); // MSGIDs are never zero in IMAP-land
|
|
Assert(0 != idEndOfRange);
|
|
|
|
|
|
if (TRUE == bSuppressComma)
|
|
lpszComma = "";
|
|
else
|
|
lpszComma = ",";
|
|
|
|
if (idStartOfRange == idEndOfRange)
|
|
// Single message number
|
|
numCharsWritten = wnsprintf(*ppDest, cchSizeDest, "%s%lu", lpszComma, idStartOfRange);
|
|
else
|
|
// Range of consecutive message numbers
|
|
numCharsWritten = wnsprintf(*ppDest, cchSizeDest, "%s%lu:%lu", lpszComma,
|
|
idStartOfRange, idEndOfRange);
|
|
|
|
*ppDest += numCharsWritten;
|
|
} // AppendMsgRange
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: EnqueueFragment
|
|
//
|
|
// Purpose:
|
|
// This function takes an IMAP_LINE_FRAGMENT and appends it to the end of
|
|
// the given IMAP_LINEFRAG_QUEUE.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT *pilfSourceFragment [in] - a pointer to the line
|
|
// fragment to enqueue in the given line fragment queue. This can be
|
|
// a single line fragment (with the pilfNextFragment member set to NULL),
|
|
// or a chain of line fragments.
|
|
// IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
|
|
// fragment queue which the given line fragment(s) should be appended to.
|
|
//
|
|
// Returns:
|
|
// Nothing. Given valid arguments, this function cannot fail.
|
|
//***************************************************************************
|
|
void CImap4Agent::EnqueueFragment(IMAP_LINE_FRAGMENT *pilfSourceFragment,
|
|
IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfLast;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pilfSourceFragment);
|
|
Assert(NULL != pilqLineFragQueue);
|
|
|
|
// Check for empty queue
|
|
pilfSourceFragment->pilfPrevFragment = pilqLineFragQueue->pilfLastFragment;
|
|
if (NULL == pilqLineFragQueue->pilfLastFragment) {
|
|
Assert(NULL == pilqLineFragQueue->pilfFirstFragment); // True test for emptiness
|
|
pilqLineFragQueue->pilfFirstFragment = pilfSourceFragment;
|
|
}
|
|
else
|
|
pilqLineFragQueue->pilfLastFragment->pilfNextFragment = pilfSourceFragment;
|
|
|
|
|
|
// Find end of queue
|
|
pilfLast = pilfSourceFragment;
|
|
while (NULL != pilfLast->pilfNextFragment)
|
|
pilfLast = pilfLast->pilfNextFragment;
|
|
|
|
pilqLineFragQueue->pilfLastFragment = pilfLast;
|
|
} // EnqueueFragment
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: InsertFragmentBeforePause
|
|
//
|
|
// Purpose:
|
|
// This function inserts the given IMAP line fragment into the given
|
|
// linefrag queue, before the first iltPAUSE element that it finds. If no
|
|
// iltPAUSE fragment could be found, the line fragment is added to the end.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT *pilfSourceFragment [in] - a pointer to the line
|
|
// fragment to insert before the iltPAUSE element in the given line
|
|
// fragment queue. This can be a single line fragment (with the
|
|
// pilfNextFragment member set to NULL), or a chain of line fragments.
|
|
// IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
|
|
// fragment queue which contains the iltPAUSE element.
|
|
//***************************************************************************
|
|
void CImap4Agent::InsertFragmentBeforePause(IMAP_LINE_FRAGMENT *pilfSourceFragment,
|
|
IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfInsertionPt, *pilfPause;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pilfSourceFragment);
|
|
Assert(NULL != pilqLineFragQueue);
|
|
|
|
// Look for the iltPAUSE fragment in the linefrag queue
|
|
pilfInsertionPt = NULL;
|
|
pilfPause = pilqLineFragQueue->pilfFirstFragment;
|
|
while (NULL != pilfPause && iltPAUSE != pilfPause->iltFragmentType) {
|
|
pilfInsertionPt = pilfPause;
|
|
pilfPause = pilfPause->pilfNextFragment;
|
|
}
|
|
|
|
if (NULL == pilfPause) {
|
|
// Didn't find iltPAUSE fragment, insert at tail of queue
|
|
AssertSz(FALSE, "Didn't find iltPAUSE fragment! WHADDUP?");
|
|
EnqueueFragment(pilfSourceFragment, pilqLineFragQueue);
|
|
}
|
|
else {
|
|
IMAP_LINE_FRAGMENT *pilfLast;
|
|
|
|
// Find the end of the source fragment
|
|
pilfLast = pilfSourceFragment;
|
|
while (NULL != pilfLast->pilfNextFragment)
|
|
pilfLast = pilfLast->pilfNextFragment;
|
|
|
|
// Found an iltPAUSE fragment. Insert the line fragment in front of it
|
|
pilfLast->pilfNextFragment = pilfPause;
|
|
Assert(pilfInsertionPt == pilfPause->pilfPrevFragment);
|
|
pilfPause->pilfPrevFragment = pilfLast;
|
|
if (NULL == pilfInsertionPt) {
|
|
// Insert at the head of the linefrag queue
|
|
Assert(pilfPause == pilqLineFragQueue->pilfFirstFragment);
|
|
pilfSourceFragment->pilfPrevFragment = NULL;
|
|
pilqLineFragQueue->pilfFirstFragment = pilfSourceFragment;
|
|
}
|
|
else {
|
|
// Insert in middle of queue
|
|
Assert(pilfPause == pilfInsertionPt->pilfNextFragment);
|
|
pilfSourceFragment->pilfPrevFragment = pilfInsertionPt;
|
|
pilfInsertionPt->pilfNextFragment = pilfSourceFragment;
|
|
}
|
|
}
|
|
} // InsertFragmentBeforePause
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DequeueFragment
|
|
//
|
|
// Purpose:
|
|
// This function returns the next line fragment from the given line
|
|
// fragment queue, removing the returned element from the queue.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
|
|
// fragment queue to dequeue from.
|
|
//
|
|
// Returns:
|
|
// A pointer to an IMAP_LINE_FRAGMENT. If none are available, NULL is
|
|
// returned.
|
|
//***************************************************************************
|
|
IMAP_LINE_FRAGMENT *CImap4Agent::DequeueFragment(IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
|
|
{
|
|
IMAP_LINE_FRAGMENT *pilfResult;
|
|
|
|
// Refcount can be 0 if we're destructing CImap4Agent while a cmd is in progress
|
|
Assert(m_lRefCount >= 0);
|
|
Assert(NULL != pilqLineFragQueue);
|
|
|
|
// Return element at head of queue, including NULL if empty queue
|
|
pilfResult = pilqLineFragQueue->pilfFirstFragment;
|
|
|
|
if (NULL != pilfResult) {
|
|
// Dequeue the element from list
|
|
pilqLineFragQueue->pilfFirstFragment = pilfResult->pilfNextFragment;
|
|
if (NULL == pilqLineFragQueue->pilfFirstFragment)
|
|
// Queue is now empty, so reset ptr to last fragment
|
|
pilqLineFragQueue->pilfLastFragment = NULL;
|
|
else {
|
|
Assert(pilfResult == pilqLineFragQueue->pilfFirstFragment->pilfPrevFragment);
|
|
pilqLineFragQueue->pilfFirstFragment->pilfPrevFragment = NULL;
|
|
}
|
|
|
|
pilfResult->pilfNextFragment = NULL;
|
|
pilfResult->pilfPrevFragment = NULL;
|
|
}
|
|
else {
|
|
AssertSz(FALSE, "Someone just tried to dequeue an element from empty queue");
|
|
}
|
|
|
|
return pilfResult;
|
|
} // DequeueFragment
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FreeFragment
|
|
//
|
|
// Purpose:
|
|
// This function frees the given IMAP line fragment and the string or
|
|
// stream data associated with it.
|
|
//
|
|
// Arguments:
|
|
// IMAP_LINE_FRAGMENT **ppilfFragment [in/out] - a pointer to the line
|
|
// fragment to free. The pointer is set to NULL after the fragment
|
|
// is freed.
|
|
//
|
|
// Returns:
|
|
// Nothing. Given valid arguments, this function cannot fail.
|
|
//***************************************************************************
|
|
void CImap4Agent::FreeFragment(IMAP_LINE_FRAGMENT **ppilfFragment)
|
|
{
|
|
// Refcount can be 0 if we're destructing CImap4Agent while a cmd is in progress
|
|
Assert(m_lRefCount >= 0);
|
|
Assert(NULL != ppilfFragment);
|
|
Assert(NULL != *ppilfFragment);
|
|
|
|
if (iltRANGELIST == (*ppilfFragment)->iltFragmentType) {
|
|
(*ppilfFragment)->data.prlRangeList->Release();
|
|
}
|
|
else if (ilsSTREAM == (*ppilfFragment)->ilsLiteralStoreType) {
|
|
Assert(iltLITERAL == (*ppilfFragment)->iltFragmentType);
|
|
(*ppilfFragment)->data.pstmSource->Release();
|
|
}
|
|
else {
|
|
Assert(ilsSTRING == (*ppilfFragment)->ilsLiteralStoreType);
|
|
SafeMemFree((*ppilfFragment)->data.pszSource);
|
|
}
|
|
|
|
delete *ppilfFragment;
|
|
*ppilfFragment = NULL;
|
|
} // FreeFragment
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SubmitIMAPCommand
|
|
//
|
|
// Purpose:
|
|
// This function takes a completed CIMAPCmdInfo structure (with completed
|
|
// command line) and submits it for transmittal to the IMAP server.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciCommand [in] - this is the completed CIMAPCmdInfo
|
|
// structure to transmit to the IMAP server.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::SubmitIMAPCommand(CIMAPCmdInfo *piciCommand)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != piciCommand);
|
|
|
|
// SubmitIMAPCommand is used to send all commands to the IMAP server.
|
|
// This is a good time to clear m_szLastResponseText
|
|
*m_szLastResponseText = '\0';
|
|
|
|
// If currently transmitted command is a paused IDLE command, unpause us
|
|
if (m_fIDLE && NULL != m_piciCmdInSending &&
|
|
icIDLE_COMMAND == m_piciCmdInSending->icCommandID &&
|
|
iltPAUSE == m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment->iltFragmentType)
|
|
ProcessSendQueue(iseUNPAUSE);
|
|
|
|
// Enqueue the command into the send queue
|
|
EnterCriticalSection(&m_csSendQueue);
|
|
if (NULL == m_piciSendQueue)
|
|
// Insert command into empty queue
|
|
m_piciSendQueue = piciCommand;
|
|
else {
|
|
CIMAPCmdInfo *pici;
|
|
|
|
// Find the end of the send queue
|
|
pici = m_piciSendQueue;
|
|
while (NULL != pici->piciNextCommand)
|
|
pici = pici->piciNextCommand;
|
|
|
|
pici->piciNextCommand = piciCommand;
|
|
}
|
|
LeaveCriticalSection(&m_csSendQueue);
|
|
|
|
// Command is queued: kickstart its send process
|
|
ProcessSendQueue(iseSEND_COMMAND);
|
|
|
|
return S_OK;
|
|
} // SubmitIMAPCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DequeueCommand
|
|
//
|
|
// Purpose:
|
|
// This function removes the command currently being sent from the send
|
|
// queue and returns a pointer to it.
|
|
//
|
|
// Returns:
|
|
// Pointer to CIMAPCmdInfo object if successful, otherwise NULL.
|
|
//***************************************************************************
|
|
CIMAPCmdInfo *CImap4Agent::DequeueCommand(void)
|
|
{
|
|
CIMAPCmdInfo *piciResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
EnterCriticalSection(&m_csSendQueue);
|
|
piciResult = m_piciCmdInSending;
|
|
m_piciCmdInSending = NULL;
|
|
if (NULL != piciResult) {
|
|
CIMAPCmdInfo *piciCurrent, *piciPrev;
|
|
|
|
// Find the command in sending in the send queue
|
|
piciCurrent = m_piciSendQueue;
|
|
piciPrev = NULL;
|
|
while (NULL != piciCurrent) {
|
|
if (piciCurrent == piciResult)
|
|
break; // Found the current command in sending
|
|
|
|
piciPrev = piciCurrent;
|
|
piciCurrent = piciCurrent->piciNextCommand;
|
|
}
|
|
|
|
// Unlink the command from the send queue
|
|
if (NULL == piciPrev)
|
|
// Unlink command from the head of the send queue
|
|
m_piciSendQueue = m_piciSendQueue->piciNextCommand;
|
|
else if (NULL != piciCurrent)
|
|
// Unlink command from the middle/end of the queue
|
|
piciPrev->piciNextCommand = piciCurrent->piciNextCommand;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csSendQueue);
|
|
return piciResult;
|
|
} // DequeueCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: AddPendingCommand
|
|
//
|
|
// Purpose:
|
|
// This function adds the given CIMAPCmdInfo object to the list of commands
|
|
// pending server responses.
|
|
//
|
|
// Arguments:
|
|
// CIMAPCmdInfo *piciNewCommand [in] - pointer to command to add to list.
|
|
//***************************************************************************
|
|
void CImap4Agent::AddPendingCommand(CIMAPCmdInfo *piciNewCommand)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
// Just insert at the head of the list
|
|
EnterCriticalSection(&m_csPendingList);
|
|
piciNewCommand->piciNextCommand = m_piciPendingList;
|
|
m_piciPendingList = piciNewCommand;
|
|
LeaveCriticalSection(&m_csPendingList);
|
|
} // AddPendingCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: RemovePendingCommand
|
|
//
|
|
// Purpose:
|
|
// This function looks for a command in the pending command list which
|
|
// matches the given tag. If found, it unlinks the CIMAPCmdInfo object from
|
|
// the list and returns a pointer to it.
|
|
//
|
|
// Arguments:
|
|
// LPSTR pszTag [in] - the tag of the command which should be removed.
|
|
//
|
|
// Returns:
|
|
// Pointer to CIMAPCmdInfo object if successful, otherwise NULL.
|
|
//***************************************************************************
|
|
CIMAPCmdInfo *CImap4Agent::RemovePendingCommand(LPSTR pszTag)
|
|
{
|
|
CIMAPCmdInfo *piciPrev, *piciCurrent;
|
|
boolean bFoundMatch;
|
|
boolean fLeaveBusy = FALSE;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszTag);
|
|
|
|
EnterCriticalSection(&m_csPendingList);
|
|
|
|
// Look for matching tag in pending command list
|
|
bFoundMatch = FALSE;
|
|
piciPrev = NULL;
|
|
piciCurrent = m_piciPendingList;
|
|
while (NULL != piciCurrent) {
|
|
if (0 == lstrcmp(pszTag, piciCurrent->szTag)) {
|
|
bFoundMatch = TRUE;
|
|
break;
|
|
}
|
|
|
|
// Advance ptrs
|
|
piciPrev = piciCurrent;
|
|
piciCurrent = piciCurrent->piciNextCommand;
|
|
}
|
|
|
|
if (FALSE == bFoundMatch)
|
|
goto exit;
|
|
|
|
// OK, we found the matching command. Unlink it from list
|
|
if (NULL == piciPrev)
|
|
// Unlink first element in pending list
|
|
m_piciPendingList = piciCurrent->piciNextCommand;
|
|
else
|
|
// Unlink element from middle/end of list
|
|
piciPrev->piciNextCommand = piciCurrent->piciNextCommand;
|
|
|
|
// If we have removed the last pending command and no commands are being
|
|
// transmitted, it's time to leave the busy section
|
|
if (NULL == m_piciPendingList && NULL == m_piciCmdInSending)
|
|
fLeaveBusy = TRUE;
|
|
|
|
exit:
|
|
LeaveCriticalSection(&m_csPendingList);
|
|
|
|
// Now we're out of &m_csPendingList, call LeaveBusy (needs m_cs). Avoids deadlock.
|
|
if (fLeaveBusy)
|
|
LeaveBusy(); // Typically not needed, anymore
|
|
|
|
if (NULL != piciCurrent)
|
|
piciCurrent->piciNextCommand = NULL;
|
|
|
|
return piciCurrent;
|
|
} // RemovePendingCommand
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetTransactionID
|
|
//
|
|
// Purpose:
|
|
// This function maps an IMAP_RESPONSE_ID to a transaction ID. This function
|
|
// takes the given IMAP_RESPONSE_ID and compares it with the IMAP command(s)
|
|
// currently pending a response. If the given response matches ONE (and only
|
|
// one) of the pending IMAP commands, then the transaction ID of that IMAP
|
|
// command is returned. If none or more than one match the given response,
|
|
// or if the response in general is unsolicited, then a value of 0 is
|
|
// returned.
|
|
//
|
|
// Arguments:
|
|
// WPARAM *pwParam [out] - the wParam for the given response. If conflicts
|
|
// could not be resolved, then a value of 0 is returned.
|
|
// LPARAM *plParam [out] - the lParam for the given response. If conflicts
|
|
// could not be resolved, then a value of 0 is returned.
|
|
// IIMAPCallback **ppCBHandler [out] - the CB Handler for a given response.
|
|
// If conflicts could not be resolved, or if a NULL CB Handler was
|
|
// specified for the associated command, the default CB handler is returned.
|
|
// IMAP_RESPONSE_ID irResponseType [in] - the response type for which the
|
|
// wants a transaction ID.
|
|
//***************************************************************************
|
|
void CImap4Agent::GetTransactionID(WPARAM *pwParam, LPARAM *plParam,
|
|
IIMAPCallback **ppCBHandler,
|
|
IMAP_RESPONSE_ID irResponseType)
|
|
{
|
|
WPARAM wParam;
|
|
LPARAM lParam;
|
|
IIMAPCallback *pCBHandler;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
wParam = 0;
|
|
lParam = 0;
|
|
pCBHandler = m_pCBHandler;
|
|
switch (irResponseType) {
|
|
// The following responses are ALWAYS expected, regardless of cmd
|
|
case irOK_RESPONSE:
|
|
case irNO_RESPONSE:
|
|
case irBAD_RESPONSE:
|
|
case irNONE: // Usually indicates parsing error (reported via ErrorNotification CB)
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler, icALL_COMMANDS);
|
|
break; // Always treat as solicited, so caller can associate with cmd
|
|
|
|
|
|
// The following responses are always unsolicited, either because
|
|
// they really ARE always unsolicited, or we don't care, or we want
|
|
// to encourage the client to expect a given response at all times
|
|
case irALERT_RESPONSECODE: // Clearly unsolicited
|
|
case irPARSE_RESPONSECODE: // Clearly unsolicited
|
|
case irPREAUTH_RESPONSE: // Clearly unsolicited
|
|
case irEXPUNGE_RESPONSE: // Client can get this any time, so get used to it
|
|
case irCMD_CONTINUATION: // No callback involved, don't care
|
|
case irBYE_RESPONSE: // Can happen at any time
|
|
case irEXISTS_RESPONSE: // Client can get this any time, so get used to it
|
|
case irRECENT_RESPONSE: // Client can get this any time, so get used to it
|
|
case irUNSEEN_RESPONSECODE: // Client can get this any time, so get used to it
|
|
case irSTATUS_RESPONSE:
|
|
break; // Always treated as unsolicited
|
|
|
|
|
|
// The following response types are considered solicited only for
|
|
// certain commands. Otherwise, they're unsolicited.
|
|
case irFLAGS_RESPONSE:
|
|
case irPERMANENTFLAGS_RESPONSECODE:
|
|
case irREADWRITE_RESPONSECODE:
|
|
case irREADONLY_RESPONSECODE:
|
|
case irUIDVALIDITY_RESPONSECODE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icSELECT_COMMAND, icEXAMINE_COMMAND);
|
|
break; // case irFLAGS_RESPONSE
|
|
|
|
case irCAPABILITY_RESPONSE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icCAPABILITY_COMMAND);
|
|
break; // case irCAPABILITY_RESPONSE
|
|
|
|
case irLIST_RESPONSE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icLIST_COMMAND);
|
|
break; // case irLIST_RESPONSE
|
|
|
|
case irLSUB_RESPONSE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icLSUB_COMMAND);
|
|
break; // case irLSUB_RESPONSE
|
|
|
|
case irSEARCH_RESPONSE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icSEARCH_COMMAND);
|
|
break; // case irSEARCH_RESPONSE
|
|
|
|
case irFETCH_RESPONSE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icFETCH_COMMAND, icSTORE_COMMAND);
|
|
break; // case irFETCH_RESPONSE
|
|
|
|
case irTRYCREATE_RESPONSECODE:
|
|
FindTransactionID(&wParam, &lParam, &pCBHandler,
|
|
icAPPEND_COMMAND, icCOPY_COMMAND);
|
|
break; // case irTRYCREATE_RESPONSECODE
|
|
|
|
|
|
default:
|
|
Assert(FALSE);
|
|
break; // default case
|
|
} // switch (irResponseType)
|
|
|
|
*pwParam = wParam;
|
|
*plParam = lParam;
|
|
*ppCBHandler = pCBHandler;
|
|
} // GetTransactionID
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FindTransactionID
|
|
//
|
|
// Purpose:
|
|
// This function traverses the pending command list searching for commands
|
|
// which match the command types specified in the arguments. If ONE (and only
|
|
// one) match is found, then its transaction ID is returne. If none or more
|
|
// than one match is found, then a transaction ID of 0 is returned.
|
|
//
|
|
// Arguments:
|
|
// WPARAM *pwParam [out] - the wParam for the given commands. If conflicts
|
|
// could not be resolved, then a value of 0 is returned. Pass NULL if
|
|
// you are not interested in this value.
|
|
// LPARAM *plParam [out] - the lParam for the given commands. If conflicts
|
|
// could not be resolved, then a value of 0 is returned. Pass NULL if
|
|
// you are not interested in this value.
|
|
// IIMAPCallback **ppCBHandler [out] - the CB Handler for a given response.
|
|
// If conflicts could not be resolved, or if a NULL CB Handler was
|
|
// specified for the associated command, the default CB handler is returned.
|
|
// Pass NULL if you are not interested in this value.
|
|
// IMAP_COMMAND icTarget1 [in] - one of the commands we're looking for in
|
|
// the pending command queue.
|
|
// IMAP_COMMAND icTarget2 [in] - another command we're looking for in
|
|
// the pending command queue.
|
|
//
|
|
// Returns:
|
|
// 0 if no matches were found
|
|
// 1 if exactly one match was found
|
|
// 2 if two matches was found. Note that there may be MORE than two matches
|
|
// in the pending list. This function gives up after it finds two matches.
|
|
//***************************************************************************
|
|
WORD CImap4Agent::FindTransactionID (WPARAM *pwParam, LPARAM *plParam,
|
|
IIMAPCallback **ppCBHandler,
|
|
IMAP_COMMAND icTarget1, IMAP_COMMAND icTarget2)
|
|
{
|
|
CIMAPCmdInfo *piciCurrentCmd;
|
|
WPARAM wParam;
|
|
LPARAM lParam;
|
|
IIMAPCallback *pCBHandler;
|
|
WORD wNumberOfMatches;
|
|
boolean bMatchAllCmds;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
if (icALL_COMMANDS == icTarget1 ||
|
|
icALL_COMMANDS == icTarget2)
|
|
bMatchAllCmds = TRUE;
|
|
else
|
|
bMatchAllCmds = FALSE;
|
|
|
|
wNumberOfMatches = 0;
|
|
wParam = 0;
|
|
lParam = 0;
|
|
pCBHandler = m_pCBHandler;
|
|
EnterCriticalSection(&m_csPendingList);
|
|
piciCurrentCmd = m_piciPendingList;
|
|
while (NULL != piciCurrentCmd) {
|
|
if (bMatchAllCmds ||
|
|
icTarget1 == piciCurrentCmd->icCommandID ||
|
|
icTarget2 == piciCurrentCmd->icCommandID) {
|
|
wParam = piciCurrentCmd->wParam;
|
|
lParam = piciCurrentCmd->lParam;
|
|
pCBHandler = piciCurrentCmd->pCBHandler;
|
|
|
|
wNumberOfMatches += 1;
|
|
}
|
|
|
|
if (wNumberOfMatches > 1) {
|
|
wParam = 0;
|
|
lParam = 0;
|
|
pCBHandler = m_pCBHandler; // Found more than one match, can't resolve transaction ID
|
|
break;
|
|
}
|
|
|
|
piciCurrentCmd = piciCurrentCmd->piciNextCommand;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csPendingList);
|
|
if (NULL != pwParam)
|
|
*pwParam = wParam;
|
|
if (NULL != plParam)
|
|
*plParam = lParam;
|
|
if (NULL != ppCBHandler)
|
|
*ppCBHandler = pCBHandler;
|
|
|
|
return wNumberOfMatches;
|
|
} // FindTransactionID
|
|
|
|
|
|
|
|
//===========================================================================
|
|
// Message Sequence Number to UID Conversion Code
|
|
//===========================================================================
|
|
//***************************************************************************
|
|
// Function: NewIRangeList
|
|
//
|
|
// Purpose:
|
|
// This function returns a pointer to an IRangeList. Its purpose is to
|
|
// allow full functionality from an IIMAPTransport pointer without needing
|
|
// to resort to CoCreateInstance to get an IRangeList.
|
|
//
|
|
// Arguments:
|
|
// IRangeList **pprlNewRangeList [out] - if successful, the function
|
|
// returns a pointer to the new IRangeList.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::NewIRangeList(IRangeList **pprlNewRangeList)
|
|
{
|
|
if (NULL == pprlNewRangeList)
|
|
return E_INVALIDARG;
|
|
|
|
*pprlNewRangeList = (IRangeList *) new CRangeList;
|
|
if (NULL == *pprlNewRangeList)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
} // NewIRangeList
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnIMAPError
|
|
//
|
|
// Purpose:
|
|
// This function calls ITransportCallback::OnError with the given info.
|
|
//
|
|
// Arguments:
|
|
// HRESULT hrResult [in] - the error code to use for IXPRESULT::hrResult.
|
|
// LPSTR pszFailureText [in] - a text string describing the failure. This
|
|
// is duplicated for IXPRESULT::pszProblem.
|
|
// BOOL bIncludeLastResponse [in] - if TRUE, this function duplicates
|
|
// the contents of m_szLastResponseText into IXPRESULT::pszResponse.
|
|
// If FALSE, IXPRESULT::pszResponse is left blank. Generally,
|
|
// m_szLastResponseText holds valid information only for errors which
|
|
// occur during the receipt of an IMAP response. Transmit errors should
|
|
// set this argument to FALSE.
|
|
// LPSTR pszDetails [in] - if bIncludeLastResponse is FALSE, the caller
|
|
// may pass a string to place into IXPRESULT::pszResponse here. If none
|
|
// is desired, the user should pass NULL.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnIMAPError(HRESULT hrResult, LPSTR pszFailureText,
|
|
BOOL bIncludeLastResponse, LPSTR pszDetails)
|
|
{
|
|
IXPRESULT rIxpResult;
|
|
|
|
if (NULL == m_pCallback)
|
|
return; // We can't do a damned thing (this can happen due to HandsOffCallback)
|
|
|
|
// Save current state
|
|
rIxpResult.hrResult = hrResult;
|
|
|
|
if (bIncludeLastResponse) {
|
|
AssertSz(NULL == pszDetails, "Can't have it both ways, buddy!");
|
|
rIxpResult.pszResponse = PszDupA(m_szLastResponseText);
|
|
}
|
|
else
|
|
rIxpResult.pszResponse = PszDupA(pszDetails);
|
|
|
|
rIxpResult.uiServerError = 0;
|
|
rIxpResult.hrServerError = S_OK;
|
|
rIxpResult.dwSocketError = m_pSocket->GetLastError();
|
|
rIxpResult.pszProblem = PszDupA(pszFailureText);
|
|
|
|
// Suspend watchdog during callback
|
|
LeaveBusy();
|
|
|
|
// Log it
|
|
if (m_pLogFile) {
|
|
int iLengthOfSz;
|
|
char sz[64];
|
|
LPSTR pszErrorText;
|
|
CByteStream bstmErrLog;
|
|
|
|
// wnsprintf is limited to an output of 1024 bytes. Use a stream.
|
|
bstmErrLog.Write("ERROR: \"", 8, NULL); // Ignore IStream::Write errors
|
|
bstmErrLog.Write(pszFailureText, lstrlen(pszFailureText), NULL);
|
|
if (bIncludeLastResponse || NULL == pszDetails)
|
|
iLengthOfSz = wnsprintf(sz, ARRAYSIZE(sz), "\", hr=0x%lX", hrResult);
|
|
else {
|
|
bstmErrLog.Write("\" (", 3, NULL);
|
|
bstmErrLog.Write(pszDetails, lstrlen(pszDetails), NULL);
|
|
iLengthOfSz = wnsprintf(sz, ARRAYSIZE(sz), "), hr=0x%lX", hrResult);
|
|
}
|
|
bstmErrLog.Write(sz, iLengthOfSz, NULL);
|
|
|
|
if (SUCCEEDED(bstmErrLog.HrAcquireStringA(NULL, &pszErrorText, ACQ_COPY)))
|
|
m_pLogFile->WriteLog(LOGFILE_DB, pszErrorText);
|
|
}
|
|
|
|
// Give to callback
|
|
m_pCallback->OnError(m_status, &rIxpResult, THIS_IInternetTransport);
|
|
|
|
// Restore the watchdog if required
|
|
if (FALSE == m_fBusy &&
|
|
(NULL != m_piciPendingList || (NULL != m_piciCmdInSending &&
|
|
icIDLE_COMMAND != m_piciCmdInSending->icCommandID))) {
|
|
hrResult = HrEnterBusy();
|
|
Assert(SUCCEEDED(hrResult));
|
|
}
|
|
|
|
// Free duplicated strings
|
|
SafeMemFree(rIxpResult.pszResponse);
|
|
SafeMemFree(rIxpResult.pszProblem);
|
|
} // OnIMAPError
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: HandsOffCallback
|
|
//
|
|
// Purpose:
|
|
// This function guarantees that the default callback handler will not be
|
|
// called from this point on, even if it has commands in the air. The pointer
|
|
// to the default CB handler is released and removed from all commands in
|
|
// the air and from the default CB handler module variable. NOTE that non-
|
|
// default CB handlers are not affected by this call.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::HandsOffCallback(void)
|
|
{
|
|
CIMAPCmdInfo *pCurrentCmd;
|
|
|
|
// Check current status
|
|
if (NULL == m_pCBHandler) {
|
|
Assert(NULL == m_pCallback);
|
|
return S_OK; // We're already done
|
|
}
|
|
|
|
// Remove default CB handler from all cmds in send queue
|
|
// NB: No need to deal with m_piciCmdInSending, since it points into this queue
|
|
pCurrentCmd = m_piciSendQueue;
|
|
while (NULL != pCurrentCmd) {
|
|
if (pCurrentCmd->pCBHandler == m_pCBHandler) {
|
|
pCurrentCmd->pCBHandler->Release();
|
|
pCurrentCmd->pCBHandler = NULL;
|
|
}
|
|
|
|
pCurrentCmd = pCurrentCmd->piciNextCommand;
|
|
}
|
|
|
|
// Remove default CB handler from all cmds in pending command queue
|
|
pCurrentCmd = m_piciPendingList;
|
|
while (NULL != pCurrentCmd) {
|
|
if (pCurrentCmd->pCBHandler == m_pCBHandler) {
|
|
pCurrentCmd->pCBHandler->Release();
|
|
pCurrentCmd->pCBHandler = NULL;
|
|
}
|
|
|
|
pCurrentCmd = pCurrentCmd->piciNextCommand;
|
|
}
|
|
|
|
// Remove default CB handler from CImap4Agent and CIxpBase module vars
|
|
m_pCBHandler->Release();
|
|
m_pCBHandler = NULL;
|
|
|
|
m_pCallback->Release();
|
|
m_pCallback = NULL;
|
|
return S_OK;
|
|
} // HandsOffCallback
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FreeAllData
|
|
//
|
|
// Purpose:
|
|
// This function deallocates the send and receive queues, the
|
|
// MsgSeqNumToUID table, and the authentication mechanism list.
|
|
//
|
|
// Arguments:
|
|
// HRESULT hrTerminatedCmdResult [in] - if a command is found in the send
|
|
// or pending queue, we must issue a cmd completion notification. This
|
|
// argument tells us what hrResult to return. It must indicate FAILURE.
|
|
//***************************************************************************
|
|
void CImap4Agent::FreeAllData(HRESULT hrTerminatedCmdResult)
|
|
{
|
|
Assert(FAILED(hrTerminatedCmdResult)); // If cmds pending, we FAILED
|
|
char szBuf[MAX_RESOURCESTRING];
|
|
|
|
FreeAuthStatus();
|
|
|
|
// Clean up the receive queue
|
|
if (NULL != m_ilqRecvQueue.pilfFirstFragment) {
|
|
DWORD dwMsgSeqNum;
|
|
|
|
// If receive queue holds a FETCH response, and if the client has stored
|
|
// non-NULL cookies in m_fbpFetchBodyPartInProgress, notify caller that it's over
|
|
if (isFetchResponse(&m_ilqRecvQueue, &dwMsgSeqNum) &&
|
|
(NULL != m_fbpFetchBodyPartInProgress.lpFetchCookie1 ||
|
|
NULL != m_fbpFetchBodyPartInProgress.lpFetchCookie2)) {
|
|
FETCH_CMD_RESULTS_EX fetchResults;
|
|
FETCH_CMD_RESULTS fcrOldFetchStruct;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
ZeroMemory(&fetchResults, sizeof(fetchResults));
|
|
fetchResults.dwMsgSeqNum = dwMsgSeqNum;
|
|
fetchResults.lpFetchCookie1 = m_fbpFetchBodyPartInProgress.lpFetchCookie1;
|
|
fetchResults.lpFetchCookie2 = m_fbpFetchBodyPartInProgress.lpFetchCookie2;
|
|
|
|
irIMAPResponse.wParam = 0;
|
|
irIMAPResponse.lParam = 0;
|
|
irIMAPResponse.hrResult = hrTerminatedCmdResult;
|
|
irIMAPResponse.lpszResponseText = NULL; // Not relevant
|
|
|
|
if (IMAP_FETCHEX_ENABLE & m_dwFetchFlags)
|
|
{
|
|
irIMAPResponse.irtResponseType = irtUPDATE_MSG_EX;
|
|
irIMAPResponse.irdResponseData.pFetchResultsEx = &fetchResults;
|
|
}
|
|
else
|
|
{
|
|
DowngradeFetchResponse(&fcrOldFetchStruct, &fetchResults);
|
|
irIMAPResponse.irtResponseType = irtUPDATE_MSG;
|
|
irIMAPResponse.irdResponseData.pFetchResults = &fcrOldFetchStruct;
|
|
}
|
|
OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
while (NULL != m_ilqRecvQueue.pilfFirstFragment) {
|
|
IMAP_LINE_FRAGMENT *pilf;
|
|
|
|
pilf = DequeueFragment(&m_ilqRecvQueue);
|
|
FreeFragment(&pilf);
|
|
} // while
|
|
} // if (receive queue not empty)
|
|
|
|
// To avoid deadlock, whenever we need to enter more than one CS, we must request
|
|
// them in the order specified in the CImap4Agent class definition. Any calls to
|
|
// OnIMAPResponse will require CIxpBase::m_cs, so enter that CS now.
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Clean up the send queue
|
|
EnterCriticalSection(&m_csSendQueue);
|
|
m_piciCmdInSending = NULL; // No need to delete obj, it points into m_piciSendQueue
|
|
while (NULL != m_piciSendQueue) {
|
|
CIMAPCmdInfo *piciDeletedCmd;
|
|
|
|
// Dequeue next command in send queue
|
|
piciDeletedCmd = m_piciSendQueue;
|
|
m_piciSendQueue = piciDeletedCmd->piciNextCommand;
|
|
|
|
// Send notification except for non-user-initiated IMAP commands
|
|
if (icIDLE_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icCAPABILITY_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icLOGIN_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icAUTHENTICATE_COMMAND != piciDeletedCmd->icCommandID) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Notify caller that his command could not be completed
|
|
LoadString(g_hLocRes, idsIMAPCmdNotSent, szBuf, ARRAYSIZE(szBuf));
|
|
irIMAPResponse.wParam = piciDeletedCmd->wParam;
|
|
irIMAPResponse.lParam = piciDeletedCmd->lParam;
|
|
irIMAPResponse.hrResult = hrTerminatedCmdResult;
|
|
irIMAPResponse.lpszResponseText = szBuf;
|
|
irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
|
|
OnIMAPResponse(piciDeletedCmd->pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
delete piciDeletedCmd;
|
|
} // while (NULL != m_piciSendQueue)
|
|
LeaveCriticalSection(&m_csSendQueue);
|
|
|
|
// Clean up the pending command queue
|
|
EnterCriticalSection(&m_csPendingList);
|
|
while (NULL != m_piciPendingList) {
|
|
CIMAPCmdInfo *piciDeletedCmd;
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
piciDeletedCmd = m_piciPendingList;
|
|
m_piciPendingList = piciDeletedCmd->piciNextCommand;
|
|
|
|
// Send notification except for non-user-initiated IMAP commands
|
|
if (icIDLE_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icCAPABILITY_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icLOGIN_COMMAND != piciDeletedCmd->icCommandID &&
|
|
icAUTHENTICATE_COMMAND != piciDeletedCmd->icCommandID) {
|
|
IMAP_RESPONSE irIMAPResponse;
|
|
|
|
// Notify caller that his command could not be completed
|
|
LoadString(g_hLocRes, idsIMAPCmdStillPending, szBuf, ARRAYSIZE(szBuf));
|
|
irIMAPResponse.wParam = piciDeletedCmd->wParam;
|
|
irIMAPResponse.lParam = piciDeletedCmd->lParam;
|
|
irIMAPResponse.hrResult = hrTerminatedCmdResult;
|
|
irIMAPResponse.lpszResponseText = szBuf;
|
|
irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
|
|
OnIMAPResponse(piciDeletedCmd->pCBHandler, &irIMAPResponse);
|
|
}
|
|
|
|
delete piciDeletedCmd;
|
|
} // while (NULL != m_piciPendingList)
|
|
LeaveCriticalSection(&m_csPendingList);
|
|
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Any literals in progress?
|
|
if (NULL != m_pilfLiteralInProgress) {
|
|
m_dwLiteralInProgressBytesLeft = 0;
|
|
FreeFragment(&m_pilfLiteralInProgress);
|
|
}
|
|
|
|
// Any fetch body parts in progress?
|
|
if (NULL != m_fbpFetchBodyPartInProgress.pszBodyTag)
|
|
MemFree(m_fbpFetchBodyPartInProgress.pszBodyTag);
|
|
|
|
m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT; // So we don't try to free pszBodyTag twice
|
|
|
|
// Free MsgSeqNumToUID table
|
|
ResetMsgSeqNumToUID();
|
|
} // FreeAllData
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FreeAuthStatus
|
|
//
|
|
// Purpose:
|
|
// This function frees the data allocated during the course of an
|
|
// authentication (all of which is stored in m_asAuthStatus).
|
|
//***************************************************************************
|
|
void CImap4Agent::FreeAuthStatus(void)
|
|
{
|
|
int i;
|
|
|
|
// Drop the authentication mechanism list
|
|
for (i=0; i < m_asAuthStatus.iNumAuthTokens; i++) {
|
|
if (NULL != m_asAuthStatus.rgpszAuthTokens[i]) {
|
|
MemFree(m_asAuthStatus.rgpszAuthTokens[i]);
|
|
m_asAuthStatus.rgpszAuthTokens[i] = NULL;
|
|
}
|
|
}
|
|
m_asAuthStatus.iNumAuthTokens = 0;
|
|
|
|
// Free up Sicily stuff
|
|
SSPIFreeContext(&m_asAuthStatus.rSicInfo);
|
|
if (NULL != m_asAuthStatus.pPackages && 0 != m_asAuthStatus.cPackages)
|
|
SSPIFreePackages(&m_asAuthStatus.pPackages, m_asAuthStatus.cPackages);
|
|
|
|
m_asAuthStatus = AuthStatus_INIT;
|
|
} // FreeAuthStatus
|
|
|
|
|
|
|
|
//===========================================================================
|
|
// CIMAPCmdInfo Class
|
|
//===========================================================================
|
|
// This class contains information about an IMAP command, such as a queue
|
|
// of line fragments which constitute the actual command, the tag of the
|
|
// command, and the transaction ID used to identify the command to the
|
|
// CImap4Agent user.
|
|
|
|
//***************************************************************************
|
|
// Function: CIMAPCmdInfo (Constructor)
|
|
// NOTE that this function deviates from convention in that its public
|
|
// module variables are NOT prefixed with a "m_". This was done to make
|
|
// access to its public module variables more readable.
|
|
//***************************************************************************
|
|
CIMAPCmdInfo::CIMAPCmdInfo(CImap4Agent *pImap4Agent,
|
|
IMAP_COMMAND icCmd, SERVERSTATE ssMinimumStateArg,
|
|
WPARAM wParamArg, LPARAM lParamArg,
|
|
IIMAPCallback *pCBHandlerArg)
|
|
{
|
|
Assert(NULL != pImap4Agent);
|
|
Assert(icNO_COMMAND != icCmd);
|
|
|
|
// Set module (that's right, module) variables
|
|
icCommandID = icCmd;
|
|
ssMinimumState = ssMinimumStateArg;
|
|
wParam = wParamArg;
|
|
lParam = lParamArg;
|
|
|
|
// Set ptr to CB Handler - if argument is NULL, substitute default CB handler
|
|
if (NULL != pCBHandlerArg)
|
|
pCBHandler = pCBHandlerArg;
|
|
else
|
|
pCBHandler = pImap4Agent->m_pCBHandler;
|
|
|
|
Assert(NULL != pCBHandler)
|
|
if (NULL != pCBHandler)
|
|
pCBHandler->AddRef();
|
|
|
|
// No AddRef() necessary, since CImap4Agent is our sole user. When they
|
|
// go, we go, and so does our pointer.
|
|
m_pImap4Agent = pImap4Agent;
|
|
|
|
pImap4Agent->GenerateCommandTag(szTag);
|
|
pilqCmdLineQueue = new IMAP_LINEFRAG_QUEUE;
|
|
*pilqCmdLineQueue = ImapLinefragQueue_INIT;
|
|
|
|
fUIDRangeList = FALSE;
|
|
piciNextCommand = NULL;
|
|
} // CIMAPCmdInfo
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ~CIMAPCmdInfo (Destructor)
|
|
//***************************************************************************
|
|
CIMAPCmdInfo::~CIMAPCmdInfo(void)
|
|
{
|
|
// Flush any unsent items from the command line queue
|
|
while (NULL != pilqCmdLineQueue->pilfFirstFragment) {
|
|
IMAP_LINE_FRAGMENT *pilf;
|
|
|
|
pilf = m_pImap4Agent->DequeueFragment(pilqCmdLineQueue);
|
|
m_pImap4Agent->FreeFragment(&pilf);
|
|
}
|
|
delete pilqCmdLineQueue;
|
|
|
|
if (NULL != pCBHandler)
|
|
pCBHandler->Release();
|
|
} // ~CIMAPCmdInfo
|
|
|
|
|
|
|
|
//===========================================================================
|
|
// Message Sequence Number to UID Conversion Code
|
|
//===========================================================================
|
|
//***************************************************************************
|
|
// Function: ResizeMsgSeqNumTable
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we receive an EXISTS response. It
|
|
// resizes the MsgSeqNumToUID table to match the current size of the mailbox.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwSizeOfMbox [in] - the number returned via the EXISTS response.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::ResizeMsgSeqNumTable(DWORD dwSizeOfMbox)
|
|
{
|
|
BOOL bResult;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
if (dwSizeOfMbox == m_dwSizeOfMsgSeqNumToUID)
|
|
return S_OK; // Nothing to do, table is already of correct size
|
|
|
|
// Check for the case where EXISTS reports new mailbox size before we
|
|
// receive the EXPUNGE cmds to notify us of deletions
|
|
if (dwSizeOfMbox < m_dwHighestMsgSeqNum) {
|
|
// Bad, bad server! (Although not strictly prohibited)
|
|
AssertSz(FALSE, "Received EXISTS before EXPUNGE commands! Check your server.");
|
|
return S_OK; // We only resize after all EXPUNGE responses have been received,
|
|
// since we don't know who to delete and since the svr expects us to
|
|
// use OLD msg seq nums until it can update us with EXPUNGE responses
|
|
// Return S_OK since this is non-fatal.
|
|
}
|
|
|
|
// Check for the case where the mailbox has become empty (MemRealloc's not as flex as realloc)
|
|
if (0 == dwSizeOfMbox) {
|
|
ResetMsgSeqNumToUID();
|
|
return S_OK;
|
|
}
|
|
|
|
// Resize the table
|
|
bResult = MemRealloc((void **)&m_pdwMsgSeqNumToUID, dwSizeOfMbox * sizeof(DWORD));
|
|
if (FALSE == bResult) {
|
|
char szTemp[MAX_RESOURCESTRING];
|
|
|
|
// Report out-of-memory error
|
|
LoadString(g_hLocRes, idsMemory, szTemp, sizeof(szTemp));
|
|
OnIMAPError(E_OUTOFMEMORY, szTemp, DONT_USE_LAST_RESPONSE);
|
|
ResetMsgSeqNumToUID();
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
else {
|
|
LONG lSizeOfUninitMemory;
|
|
|
|
// Zero any memory above m_dwHighestMsgSeqNum element to end of array
|
|
lSizeOfUninitMemory = (dwSizeOfMbox - m_dwHighestMsgSeqNum) * sizeof(DWORD);
|
|
if (0 < lSizeOfUninitMemory)
|
|
ZeroMemory(m_pdwMsgSeqNumToUID + m_dwHighestMsgSeqNum, lSizeOfUninitMemory);
|
|
|
|
m_dwSizeOfMsgSeqNumToUID = dwSizeOfMbox;
|
|
}
|
|
|
|
// Make sure we never shrink the table smaller than highest msg seq num
|
|
Assert(m_dwHighestMsgSeqNum <= m_dwSizeOfMsgSeqNumToUID);
|
|
return S_OK;
|
|
} // ResizeMsgSeqNumTable
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: UpdateSeqNumToUID
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we receive a FETCH response which has
|
|
// both a message sequence number and a UID number. It updates the
|
|
// MsgSeqNumToUID table so that given msg seq number maps to the given UID.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwMsgSeqNum [in] - the message sequence number of the FETCH
|
|
// response.
|
|
// DWORD dwUID [in] - the UID of the given message sequence number.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::UpdateSeqNumToUID(DWORD dwMsgSeqNum, DWORD dwUID)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
// Check args
|
|
if (0 == dwMsgSeqNum || 0 == dwUID) {
|
|
AssertSz(FALSE, "Zero is not an acceptable number for a msg seq num or UID.");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Check if we have a table
|
|
if (NULL == m_pdwMsgSeqNumToUID) {
|
|
// This could mean programmer error, or server never gave us EXISTS
|
|
DOUT("You're trying to update a non-existent MsgSeqNumToUID table.");
|
|
}
|
|
|
|
// We cannot check against m_dwHighestMsgSeqNum, because we update that
|
|
// variable at the end of this function! The second-best thing to do is
|
|
// to verify that we lie within m_dwSizeOfMsgSeqNum.
|
|
if (dwMsgSeqNum > m_dwSizeOfMsgSeqNumToUID || NULL == m_pdwMsgSeqNumToUID) {
|
|
HRESULT hrResult;
|
|
|
|
DOUT("Msg seq num out of range! Could be server bug, or out of memory.");
|
|
hrResult = ResizeMsgSeqNumTable(dwMsgSeqNum); // Do the robust thing: resize our table
|
|
if(FAILED(hrResult))
|
|
return hrResult;
|
|
}
|
|
|
|
// Check for screwups
|
|
// First check if a UID has been changed
|
|
if (0 != m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] &&
|
|
m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] != dwUID) {
|
|
char szTemp[MAX_RESOURCESTRING];
|
|
char szDetails[MAX_RESOURCESTRING];
|
|
|
|
wnsprintf(szDetails, ARRAYSIZE(szDetails), "MsgSeqNum %lu: Previous UID: %lu, New UID: %lu.",
|
|
dwMsgSeqNum, m_pdwMsgSeqNumToUID[dwMsgSeqNum-1], dwUID);
|
|
LoadString(g_hLocRes, idsIMAPUIDChanged, szTemp, sizeof(szTemp));
|
|
OnIMAPError(IXP_E_IMAP_CHANGEDUID, szTemp, DONT_USE_LAST_RESPONSE, szDetails);
|
|
// In this case, we'll still return S_OK, but user will know of problem
|
|
}
|
|
|
|
// Next, verify that this UID is strictly ascending: this UID should be
|
|
// strictly greater than previous UID, and stricly less than succeeding UID
|
|
// Succeeding UID can be 0 (indicates it's uninitialized)
|
|
if (1 != dwMsgSeqNum && m_pdwMsgSeqNumToUID[dwMsgSeqNum-2] >= dwUID || // Check UID below
|
|
dwMsgSeqNum < m_dwSizeOfMsgSeqNumToUID && // Check UID above
|
|
0 != m_pdwMsgSeqNumToUID[dwMsgSeqNum] &&
|
|
m_pdwMsgSeqNumToUID[dwMsgSeqNum] <= dwUID) {
|
|
char szTemp[MAX_RESOURCESTRING];
|
|
char szDetails[MAX_RESOURCESTRING];
|
|
|
|
wnsprintf(szDetails, ARRAYSIZE(szDetails), "MsgSeqNum %lu, New UID %lu. Prev UID: %lu, Next UID: %lu.",
|
|
dwMsgSeqNum, dwUID, 1 == dwMsgSeqNum ? 0 : m_pdwMsgSeqNumToUID[dwMsgSeqNum-2],
|
|
dwMsgSeqNum >= m_dwSizeOfMsgSeqNumToUID ? 0 : m_pdwMsgSeqNumToUID[dwMsgSeqNum]);
|
|
LoadString(g_hLocRes, idsIMAPUIDOrder, szTemp, sizeof(szTemp));
|
|
OnIMAPError(IXP_E_IMAP_UIDORDER, szTemp, DONT_USE_LAST_RESPONSE, szDetails);
|
|
// In this case, we'll still return S_OK, but user will know of problem
|
|
}
|
|
|
|
// Record the given UID under the given msg seq number
|
|
m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] = dwUID;
|
|
if (dwMsgSeqNum > m_dwHighestMsgSeqNum)
|
|
m_dwHighestMsgSeqNum = dwMsgSeqNum;
|
|
|
|
return S_OK;
|
|
} // UpdateSeqNumToUID
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: RemoveSequenceNum
|
|
//
|
|
// Purpose:
|
|
// This function is called whenever we receive an EXPUNGE response. It
|
|
// removes the given message sequence number from the MsgSeqNumToUID table,
|
|
// and compacts the table so that all message sequence numbers following
|
|
// the deleted one are re-sequenced.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwDeletedMsgSeqNum [in] - message sequence number of deleted msg.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::RemoveSequenceNum(DWORD dwDeletedMsgSeqNum)
|
|
{
|
|
DWORD *pdwDest, *pdwSrc;
|
|
LONG lSizeOfBlock;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
|
|
// Check arguments
|
|
if (dwDeletedMsgSeqNum > m_dwHighestMsgSeqNum || 0 == dwDeletedMsgSeqNum) {
|
|
AssertSz(FALSE, "Msg seq num out of range! Could be server bug, or out of memory.");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Check if we have a table
|
|
if (NULL == m_pdwMsgSeqNumToUID) {
|
|
// This could mean programmer error, or server never gave us EXISTS
|
|
AssertSz(FALSE, "You're trying to update a non-existent MsgSeqNumToUID table.");
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Compact the array
|
|
pdwDest = &m_pdwMsgSeqNumToUID[dwDeletedMsgSeqNum-1];
|
|
pdwSrc = pdwDest + 1;
|
|
lSizeOfBlock = (m_dwHighestMsgSeqNum - dwDeletedMsgSeqNum) * sizeof(DWORD);
|
|
if (0 < lSizeOfBlock)
|
|
MoveMemory(pdwDest, pdwSrc, lSizeOfBlock);
|
|
|
|
m_dwHighestMsgSeqNum -= 1;
|
|
|
|
// Initialize the empty element at top of array to prevent confusion
|
|
ZeroMemory(m_pdwMsgSeqNumToUID + m_dwHighestMsgSeqNum, sizeof(DWORD));
|
|
return S_OK;
|
|
} // RemoveSequenceNum
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: MsgSeqNumToUID
|
|
//
|
|
// Purpose:
|
|
// This function takes a message sequence number and converts it to a UID
|
|
// based on the MsgSeqNumToUID table.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwMsgSeqNum [in] - the sequence number for which the caller wants
|
|
// to know the UID.
|
|
// DWORD *pdwUID [out] - the UID associated with the given sequence number
|
|
// is returned here. If none could be found, this function returns 0.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::MsgSeqNumToUID(DWORD dwMsgSeqNum,
|
|
DWORD *pdwUID)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pdwUID);
|
|
|
|
// Check arguments
|
|
if (dwMsgSeqNum > m_dwHighestMsgSeqNum || 0 == dwMsgSeqNum) {
|
|
AssertSz(FALSE, "Msg seq num out of range! Could be server bug, or out of memory.");
|
|
*pdwUID = 0;
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Check if we have a table
|
|
if (NULL == m_pdwMsgSeqNumToUID) {
|
|
// This could mean programmer error, or server never gave us EXISTS
|
|
AssertSz(FALSE, "You're trying to update a non-existent MsgSeqNumToUID table.");
|
|
*pdwUID = 0;
|
|
return E_FAIL;
|
|
}
|
|
|
|
// IE5 Bug #44956: It's OK for a MsgSeqNumToUID mapping to result in a UID of 0. Sometimes an IMAP
|
|
// server can skip a range of messages. In such cases we will return a failure result.
|
|
*pdwUID = m_pdwMsgSeqNumToUID[dwMsgSeqNum-1];
|
|
if (0 == *pdwUID)
|
|
return OLE_E_BLANK;
|
|
else
|
|
return S_OK;
|
|
} // MsgSeqNumToUID
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetMsgSeqNumToUIDArray
|
|
//
|
|
// Purpose:
|
|
// This function returns a copy of the MsgSeqNumToUID array. The caller
|
|
// will want to do this to delete messages from the cache which no longer
|
|
// exist on the server, for example.
|
|
//
|
|
// Arguments:
|
|
// DWORD **ppdwMsgSeqNumToUIDArray [out] - the function returns a pointer
|
|
// to the copy of the MsgSeqNumToUID array in this argument. Note that
|
|
// it is the caller's responsibility to MemFree the array. If no array
|
|
// is available, or it is empty, the returned pointer value is NULL.
|
|
// DWORD *pdwNumberOfElements [out] - the function returns the size of
|
|
// the MsgSeqNumToUID array.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::GetMsgSeqNumToUIDArray(DWORD **ppdwMsgSeqNumToUIDArray,
|
|
DWORD *pdwNumberOfElements)
|
|
{
|
|
BOOL bResult;
|
|
DWORD dwSizeOfArray;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != ppdwMsgSeqNumToUIDArray);
|
|
Assert(NULL != pdwNumberOfElements);
|
|
|
|
// Check if our table is empty. If so, return success, but no array
|
|
if (NULL == m_pdwMsgSeqNumToUID || 0 == m_dwHighestMsgSeqNum) {
|
|
*ppdwMsgSeqNumToUIDArray = NULL;
|
|
*pdwNumberOfElements = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
// We have a non-zero-size array to return. Make a copy of our table
|
|
dwSizeOfArray = m_dwHighestMsgSeqNum * sizeof(DWORD);
|
|
bResult = MemAlloc((void **)ppdwMsgSeqNumToUIDArray, dwSizeOfArray);
|
|
if (FALSE == bResult)
|
|
return E_OUTOFMEMORY;
|
|
|
|
CopyMemory(*ppdwMsgSeqNumToUIDArray, m_pdwMsgSeqNumToUID, dwSizeOfArray);
|
|
*pdwNumberOfElements = m_dwHighestMsgSeqNum;
|
|
return S_OK;
|
|
} // GetMsgSeqNumToUIDArray
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetHighestMsgSeqNum
|
|
//
|
|
// Purpose:
|
|
// This function returns the highest message sequence number reported in
|
|
// the MsgSeqNumToUID array.
|
|
//
|
|
// Arguments:
|
|
// DWORD *pdwHighestMSN [out] - the highest message sequence number in the
|
|
// table is returned here.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::GetHighestMsgSeqNum(DWORD *pdwHighestMSN)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pdwHighestMSN);
|
|
|
|
*pdwHighestMSN = m_dwHighestMsgSeqNum;
|
|
return S_OK;
|
|
} // GetHighestMsgSeqNum
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ResetMsgSeqNumToUID
|
|
//
|
|
// Purpose:
|
|
// This function resets the variables used to maintain the MsgSeqNumToUID
|
|
// table. This function is called whenever the MsgSeqNumToUID table becomes
|
|
// invalid (say, when a new mailbox is selected, or we are disconnected).
|
|
//
|
|
// Returns:
|
|
// S_OK. This function cannot fail.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::ResetMsgSeqNumToUID(void)
|
|
{
|
|
if (NULL != m_pdwMsgSeqNumToUID) {
|
|
MemFree(m_pdwMsgSeqNumToUID);
|
|
m_pdwMsgSeqNumToUID = NULL;
|
|
}
|
|
|
|
m_dwSizeOfMsgSeqNumToUID = 0;
|
|
m_dwHighestMsgSeqNum = 0;
|
|
|
|
return S_OK;
|
|
} // ResetMsgSeqNumToUID
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isPrintableUSASCII
|
|
//
|
|
// Purpose:
|
|
// This function determines whether the given character is directly
|
|
// encodable, or whether the character must be encoded in modified IMAP UTF7,
|
|
// as outlined in RFC2060.
|
|
//
|
|
// Arguments:
|
|
// BOOL fUnicode [in] - TRUE if input string is Unicode, otherwise FALSE.
|
|
// LPCSTR pszIn [in] - pointer to char we want to verify.
|
|
//
|
|
// Returns:
|
|
// TRUE if the given character may be directly encoded. FALSE if the
|
|
// character must be encoded in UTF-7.
|
|
//***************************************************************************
|
|
inline boolean CImap4Agent::isPrintableUSASCII(BOOL fUnicode, LPCSTR pszIn)
|
|
{
|
|
WCHAR wc;
|
|
|
|
if (fUnicode)
|
|
wc = *((LPWSTR)pszIn);
|
|
else
|
|
wc = (*pszIn & 0x00FF);
|
|
|
|
if (wc >= 0x0020 && wc <= 0x0025 ||
|
|
wc >= 0x0027 && wc <= 0x007e)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
} // isPrintableUSASCII
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isIMAPModifiedBase64
|
|
//
|
|
// Purpose:
|
|
// This function determines whether the given character is in the modified
|
|
// IMAP Base64 set as defined by RFC1521, RFC1642 and RFC2060. This modified
|
|
// IMAP Base64 set is used in IMAP-modified UTF-7 encoding of mailbox names.
|
|
//
|
|
// Arguments:
|
|
// char c [in] - character to be classified.
|
|
//
|
|
// Returns:
|
|
// TRUE if given character is in the modified IMAP Base64 set, otherwise
|
|
// FALSE.
|
|
//***************************************************************************
|
|
inline boolean CImap4Agent::isIMAPModifiedBase64(const char c)
|
|
{
|
|
if (c >= 'A' && c <= 'Z' ||
|
|
c >= 'a' && c <= 'z' ||
|
|
c >= '0' && c <= '9' ||
|
|
'+' == c || ',' == c)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
} // isIMAPModifiedBase64
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: isEqualUSASCII
|
|
//
|
|
// Purpose:
|
|
// This function determines whether the given pointer points to the given
|
|
// USASCII character, based on whether we are in Unicode mode or not.
|
|
//
|
|
// Arguments:
|
|
// BOOL fUnicode [in] - TRUE if input string is Unicode, otherwise FALSE.
|
|
// LPSTR pszIn [in] - pointer to char we want to verify.
|
|
// char c [in] - the USASCII character we want to detect.
|
|
//
|
|
// Returns:
|
|
// TRUE if given character is the null terminator, otherwise, FALSE.
|
|
//***************************************************************************
|
|
inline boolean CImap4Agent::isEqualUSASCII(BOOL fUnicode, LPCSTR pszIn, const char c)
|
|
{
|
|
if (fUnicode) {
|
|
WCHAR wc = c & 0x00FF;
|
|
|
|
if (wc == *((LPWSTR)pszIn))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
else {
|
|
if (c == *pszIn)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SetUSASCIIChar
|
|
//
|
|
// Purpose:
|
|
// This function writes a USASCII character to the given string pointer.
|
|
// The purpose of this function is to allow the caller to ignore whether
|
|
// he is writing to a Unicode output or not.
|
|
//
|
|
// Arguments:
|
|
// BOOL fUnicode [in] - TRUE if target is Unicode, else FALSE.
|
|
// LPSTR pszOut [in] - pointer to character's destination. If fUnicode is
|
|
// TRUE, then two bytes will be written to this location.
|
|
// char cUSASCII [in] - the character to be written to pszOut.
|
|
//***************************************************************************
|
|
inline void CImap4Agent::SetUSASCIIChar(BOOL fUnicode, LPSTR pszOut, char cUSASCII)
|
|
{
|
|
Assert(0 == (cUSASCII & 0x80));
|
|
|
|
if (fUnicode)
|
|
{
|
|
*((LPWSTR) pszOut) = cUSASCII;
|
|
Assert(0 == (*((LPWSTR) pszOut) & 0xFF80));
|
|
}
|
|
else
|
|
*pszOut = cUSASCII;
|
|
} // SetUSASCIIChar
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: MultiByteToModifiedUTF7
|
|
//
|
|
// Purpose:
|
|
// This function takes a MultiByte string and converts it to modified IMAP
|
|
// UTF7, which is described in RFC2060.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR pszSource [in] - pointer to the MultiByte string to convert to UTF7.
|
|
// LPSTR *ppszDestination [out] - a pointer to a string buffer containing
|
|
// the UTF7 equivalent of pszSource is returned here. It is the caller's
|
|
// responsibility to MemFree this string.
|
|
// UINT uiSourceCP [in] - indicates the codepage for pszSource.
|
|
// DWORD dwFlags [in] - Reserved. Leave as 0.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::MultiByteToModifiedUTF7(LPCSTR pszSource,
|
|
LPSTR *ppszDestination,
|
|
UINT uiSourceCP,
|
|
DWORD dwFlags)
|
|
{
|
|
int iResult;
|
|
HRESULT hrResult;
|
|
BOOL fPassThrough, fSkipByte;
|
|
LPCSTR pszIn, pszStartOfLastRun;
|
|
CByteStream bstmDestination;
|
|
BOOL fUnicode;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(NULL != pszSource);
|
|
Assert(NULL != ppszDestination);
|
|
Assert(NULL != m_pInternational);
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
fPassThrough = TRUE;
|
|
fSkipByte = FALSE;
|
|
pszIn = pszSource;
|
|
pszStartOfLastRun = pszSource;
|
|
fUnicode = (CP_UNICODE == uiSourceCP);
|
|
|
|
*ppszDestination = NULL;
|
|
|
|
// Loop through the entire input str either in one of two modes:
|
|
// Passthrough, or non-US string collection (where we determine
|
|
// the length of a string which must be encoded in UTF-7).
|
|
while (1) {
|
|
|
|
// Skip over the trail bytes
|
|
if (fSkipByte) {
|
|
AssertSz(FALSE == fUnicode, "Unicode has no trail bytes");
|
|
fSkipByte = FALSE;
|
|
if ('\0' != *pszIn)
|
|
pszIn += 1;
|
|
continue;
|
|
}
|
|
|
|
if (fPassThrough) {
|
|
if (isEqualUSASCII(fUnicode, pszIn, '&') || isEqualUSASCII(fUnicode, pszIn, '\0') ||
|
|
FALSE == isPrintableUSASCII(fUnicode, pszIn)) {
|
|
// Flush USASCII characters collected until now (if any)
|
|
if (pszIn - pszStartOfLastRun > 0) {
|
|
LPSTR pszFreeMe = NULL;
|
|
LPCSTR pszUSASCII;
|
|
DWORD dwUSASCIILen = 0;
|
|
|
|
if (fUnicode) {
|
|
hrResult = UnicodeToUSASCII(&pszFreeMe, (LPCWSTR) pszStartOfLastRun,
|
|
(DWORD) (pszIn - pszStartOfLastRun), &dwUSASCIILen);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
pszUSASCII = pszFreeMe;
|
|
}
|
|
else {
|
|
pszUSASCII = pszStartOfLastRun;
|
|
dwUSASCIILen = (DWORD) (pszIn - pszStartOfLastRun);
|
|
}
|
|
|
|
hrResult = bstmDestination.Write(pszUSASCII, dwUSASCIILen, NULL);
|
|
if (NULL != pszFreeMe)
|
|
MemFree(pszFreeMe);
|
|
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
}
|
|
|
|
// Special-case the '&' character: it is converted to "&-"
|
|
if (isEqualUSASCII(fUnicode, pszIn, '&')) {
|
|
// Write "&-" to stream (always in USASCII)
|
|
hrResult = bstmDestination.Write("&-", sizeof("&-") - 1, NULL);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Reset pointers
|
|
pszStartOfLastRun = pszIn + (fUnicode ? 2 : 1); // Point past "&"
|
|
} // if ('&' == cCurrent)
|
|
else if (FALSE == isEqualUSASCII(fUnicode, pszIn, '\0')) {
|
|
Assert(FALSE == isPrintableUSASCII(fUnicode, pszIn));
|
|
|
|
// State transition: time for some UTF-7 encoding
|
|
fPassThrough = FALSE;
|
|
pszStartOfLastRun = pszIn;
|
|
if (FALSE == fUnicode && IsDBCSLeadByteEx(uiSourceCP, *pszIn))
|
|
fSkipByte = TRUE;
|
|
} // else if ('\0' != cCurrent): shortcut calc for non-printable USASCII
|
|
} // if ('&' == cCurrent || '\0' == cCurrent || FALSE == isPrintableUSASCII(cCurrent))
|
|
|
|
// Otherwise do nothing, we're collecting a run of USASCII chars
|
|
|
|
} // if (fPassThrough)
|
|
else {
|
|
// Non-US String Collection: Keep advancing through input str until
|
|
// we find a char which does not need to be encoded in UTF-7 (incl. NULL)
|
|
if (isPrintableUSASCII(fUnicode, pszIn) || isEqualUSASCII(fUnicode, pszIn, '&') ||
|
|
isEqualUSASCII(fUnicode, pszIn, '\0')) {
|
|
LPSTR pszOut = NULL;
|
|
int iNumCharsWritten;
|
|
|
|
// State transition: back to passthrough mode
|
|
fPassThrough = TRUE;
|
|
|
|
// Convert non-US string to UTF-7
|
|
hrResult = NonUSStringToModifiedUTF7(uiSourceCP, pszStartOfLastRun,
|
|
(DWORD) (pszIn - pszStartOfLastRun), &pszOut, &iNumCharsWritten);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Write modified UTF-7 string to stream
|
|
hrResult = bstmDestination.Write(pszOut, iNumCharsWritten, NULL);
|
|
MemFree(pszOut);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
pszStartOfLastRun = pszIn; // Reset for USASCII collection process
|
|
continue; // Do not advance ptr: we want current char to pass through
|
|
}
|
|
else if (FALSE == fUnicode && IsDBCSLeadByteEx(uiSourceCP, *pszIn))
|
|
fSkipByte = TRUE;
|
|
} // else-NOT-fPassThrough
|
|
|
|
// Check for end-of-input
|
|
if (isEqualUSASCII(fUnicode, pszIn, '\0'))
|
|
break; // We're done here
|
|
|
|
// Advance pointer to next character
|
|
pszIn += (fUnicode ? 2 : 1);
|
|
} // while
|
|
|
|
exit:
|
|
if (SUCCEEDED(hrResult)) {
|
|
hrResult = bstmDestination.HrAcquireStringA(NULL, ppszDestination,
|
|
ACQ_DISPLACE);
|
|
if (SUCCEEDED(hrResult))
|
|
hrResult = S_OK;
|
|
}
|
|
|
|
if (NULL == *ppszDestination && SUCCEEDED(hrResult))
|
|
hrResult = E_OUTOFMEMORY;
|
|
|
|
return hrResult;
|
|
} // MultiByteToModifiedUTF7
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: NonUSStringToModifiedUTF7
|
|
//
|
|
// Purpose:
|
|
// This function takes a string consisting of non-US-ASCII characters, and
|
|
// converts them to modified IMAP UTF-7 (described in RFC2060).
|
|
//
|
|
// Arguments:
|
|
// UINT uiCurrentACP [in] - codepage used to interpret pszStartOfNonUSASCII
|
|
// LPCSTR pszStartOfNonUSASCII [in] - string to convert to modified IMAP
|
|
// UTF-7.
|
|
// int iLengthOfNonUSASCII [in] - the number of characters in
|
|
// pszStartofNonUSASCII.
|
|
// LPSTR *ppszOut [out] - the destination for the modified IMAP UTF-7 version
|
|
// of pszStartOfNonUSASCII. This function appends a null-terminator. It is
|
|
// the caller's responsibility to call MemFree when finished with the buffer.
|
|
// LPINT piNumCharsWritten [out] - This function returns the number
|
|
// of characters written (excluding null-terminator) to *ppszOut.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::NonUSStringToModifiedUTF7(UINT uiCurrentACP,
|
|
LPCSTR pszStartOfNonUSASCII,
|
|
int iLengthOfNonUSASCII,
|
|
LPSTR *ppszOut,
|
|
LPINT piNumCharsWritten)
|
|
{
|
|
HRESULT hrResult;
|
|
int iNumCharsWritten, i;
|
|
LPSTR p;
|
|
BOOL fResult;
|
|
|
|
Assert(NULL != ppszOut);
|
|
|
|
// Initialize return values
|
|
*ppszOut = NULL;
|
|
*piNumCharsWritten = 0;
|
|
|
|
// First, convert the non-US string to standard UTF-7 (alloc 1 extra char: leave room for '-')
|
|
iNumCharsWritten = 0; // Tell ConvertString to find proper output buffer size
|
|
hrResult = ConvertString(uiCurrentACP, CP_UTF7, pszStartOfNonUSASCII,
|
|
&iLengthOfNonUSASCII, ppszOut, &iNumCharsWritten, sizeof(char));
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Now, convert standard UTF-7 to IMAP4 modified UTF-7
|
|
// Replace leading '+' with '&'. Since under IMAP UTF-7 '+' is never
|
|
// encoded, we never expect "+-" as the result. Remember output is always USASCII
|
|
if (iNumCharsWritten > 0 && '+' == **ppszOut)
|
|
**ppszOut = '&';
|
|
else {
|
|
AssertSz(FALSE, "MLANG crapped out on me.");
|
|
hrResult = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
// Replace all occurrances of '/' with ','
|
|
p = *ppszOut;
|
|
for (i = 0; i < iNumCharsWritten; i++) {
|
|
if ('/' == *p)
|
|
*p = ',';
|
|
|
|
p += 1;
|
|
}
|
|
|
|
// p now points to where null-terminator should go.
|
|
// Ensure that the UTF-7 string ends with '-'. Otherwise, put one there
|
|
// (we allocated enough room for one more char plus null-term).
|
|
if ('-' != *(p-1)) {
|
|
*p = '-';
|
|
p += 1;
|
|
iNumCharsWritten += 1;
|
|
}
|
|
|
|
// Null-terminate output string, and return values
|
|
*p = '\0';
|
|
*piNumCharsWritten = iNumCharsWritten;
|
|
|
|
exit:
|
|
if (FAILED(hrResult) && NULL != *ppszOut) {
|
|
MemFree(*ppszOut);
|
|
*ppszOut = NULL;
|
|
}
|
|
|
|
return hrResult;
|
|
} // NonUSStringToModifiedUTF7
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ModifiedUTF7ToMultiByte
|
|
//
|
|
// Purpose:
|
|
// This function takes a modified IMAP UTF-7 string (as defined in RFC2060)
|
|
// and converts it to a multi-byte string.
|
|
//
|
|
// Arguments:
|
|
// LPCSTR pszSource [in] - a null-terminated string containing the modified
|
|
// IMAP UTF-7 string to convert to multibyte.
|
|
// LPSTR *ppszDestination [out] - this function returns a pointer to the
|
|
// null-terminated multibyte string (in the system codepage) obtained
|
|
// from pszSource. It is the caller's responsiblity to MemFree this
|
|
// string when it is done with it.
|
|
// UINT uiDestintationCP [in] - indicates the desired codepage for the
|
|
// destination string.
|
|
// DWORD dwFlags [in] - Reserved. Leave as 0.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Success result codes include:
|
|
// S_OK - pszSource successfully converted to modified UTF-7
|
|
// IXP_S_IMAP_VERBATIM_MBOX - pszSource could not be converted to multibyte,
|
|
// so ppszDestination contains a duplicate of pszSource. If target CP
|
|
// is Unicode, pszSource is converted to Unicode with the assumption
|
|
// that it is USASCII. IMAP_MBOXXLATE_VERBATIMOK must have been set via
|
|
// SetDefaultCP in order to get this behaviour.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::ModifiedUTF7ToMultiByte(LPCSTR pszSource,
|
|
LPSTR *ppszDestination,
|
|
UINT uiDestinationCP,
|
|
DWORD dwFlags)
|
|
{
|
|
HRESULT hrResult;
|
|
BOOL fPassThrough, fTrailByte;
|
|
LPCSTR pszIn, pszStartOfLastRun;
|
|
CByteStream bstmDestination;
|
|
BOOL fUnicode;
|
|
|
|
// Initialize variables
|
|
hrResult = S_OK;
|
|
fPassThrough = TRUE;
|
|
fTrailByte = FALSE;
|
|
pszIn = pszSource;
|
|
pszStartOfLastRun = pszSource;
|
|
fUnicode = (CP_UNICODE == uiDestinationCP);
|
|
|
|
*ppszDestination = NULL;
|
|
|
|
// Loop through the entire input str either in one of two modes:
|
|
// Passthrough, or UTF-7 string collection (where we determine
|
|
// the length of a string which was encoded in UTF-7).
|
|
while (1) {
|
|
char cCurrent;
|
|
|
|
cCurrent = *pszIn;
|
|
if (fPassThrough) {
|
|
if ((FALSE == fTrailByte && '&' == cCurrent) || '\0' == cCurrent) {
|
|
// State transition: flush collected non-UTF7
|
|
BOOL fResult;
|
|
LPSTR pszFreeMe = NULL;
|
|
LPCSTR pszNonUTF7;
|
|
int iNonUTF7Len;
|
|
int iSrcLen;
|
|
|
|
if (fUnicode) {
|
|
// Convert non-UTF7 to Unicode
|
|
// Convert system codepage to CP_UNICODE. We KNOW source should be strictly
|
|
// USASCII, but can't assume it because some IMAP servers don't strictly
|
|
// prohibit 8-bit mailbox names. SCARY.
|
|
iSrcLen = (int) (pszIn - pszStartOfLastRun); // Pass in size of input and output buffer
|
|
iNonUTF7Len = iSrcLen * sizeof(WCHAR) / sizeof(char); // We know max output buffer size
|
|
hrResult = ConvertString(GetACP(), uiDestinationCP, pszStartOfLastRun,
|
|
&iSrcLen, &pszFreeMe, &iNonUTF7Len, 0);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
pszNonUTF7 = pszFreeMe;
|
|
}
|
|
else {
|
|
pszNonUTF7 = pszStartOfLastRun;
|
|
iNonUTF7Len = (int) (pszIn - pszStartOfLastRun);
|
|
}
|
|
|
|
hrResult = bstmDestination.Write(pszNonUTF7, iNonUTF7Len, NULL);
|
|
if (NULL != pszFreeMe)
|
|
MemFree(pszFreeMe);
|
|
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Start collecting UTF-7. Loop until we hit '-'
|
|
fPassThrough = FALSE;
|
|
pszStartOfLastRun = pszIn;
|
|
}
|
|
else {
|
|
// Non-UTF7 stuff is copied verbatim to the output: collect it. Assume
|
|
// source is in m_uiDefaultCP codepage. We SHOULD be able to assume
|
|
// source is USASCII only but some svrs are not strict about disallowing 8-bit
|
|
if (FALSE == fTrailByte && IsDBCSLeadByteEx(m_uiDefaultCP, cCurrent))
|
|
fTrailByte = TRUE;
|
|
else
|
|
fTrailByte = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
// UTF-7 collection mode: Keep going until we hit non-UTF7 char
|
|
if (FALSE == isIMAPModifiedBase64(cCurrent)) {
|
|
int iLengthOfUTF7, iNumBytesWritten, iOutputBufSize;
|
|
LPSTR pszSrc, pszDest, p;
|
|
BOOL fResult;
|
|
|
|
// State transition, time to convert some modified UTF-7
|
|
fPassThrough = TRUE;
|
|
Assert(FALSE == fTrailByte);
|
|
|
|
// If current character is '-', absorb it (don't process it)
|
|
if ('-' == cCurrent)
|
|
pszIn += 1;
|
|
|
|
// Check for "&-" or "&(end of buffer/nonBase64)" sequence
|
|
iLengthOfUTF7 = (int) (pszIn - pszStartOfLastRun);
|
|
if (2 == iLengthOfUTF7 && '-' == cCurrent ||
|
|
1 == iLengthOfUTF7) {
|
|
LPSTR psz;
|
|
DWORD dwLen;
|
|
|
|
Assert('&' == *pszStartOfLastRun);
|
|
|
|
if (fUnicode) {
|
|
psz = (LPSTR) L"&";
|
|
dwLen = 2;
|
|
}
|
|
else {
|
|
psz = "&";
|
|
dwLen = 1;
|
|
}
|
|
|
|
hrResult = bstmDestination.Write(psz, dwLen, NULL);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
pszStartOfLastRun = pszIn; // Set us up for non-UTF7 collection
|
|
continue; // Process next character normally
|
|
}
|
|
|
|
// Copy the UTF-7 sequence to a temp buffer, and
|
|
// convert modified IMAP UTF-7 to standard UTF-7
|
|
// First, duplicate the IMAP UTF-7 string so we can modify it
|
|
fResult = MemAlloc((void **)&pszSrc, iLengthOfUTF7 + 1); // Room for null-term
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
CopyMemory(pszSrc, pszStartOfLastRun, iLengthOfUTF7);
|
|
pszSrc[iLengthOfUTF7] = '\0';
|
|
|
|
// Next, replace leading '&' with '+'
|
|
Assert('&' == *pszSrc);
|
|
pszSrc[0] = '+';
|
|
|
|
// Next, replace all ',' with '/'
|
|
p = pszSrc + 1;
|
|
for (iNumBytesWritten = 1; iNumBytesWritten < iLengthOfUTF7;
|
|
iNumBytesWritten++) {
|
|
if (',' == *p)
|
|
*p = '/';
|
|
|
|
p += 1;
|
|
}
|
|
|
|
// Now convert the UTF-7 to target codepage
|
|
iNumBytesWritten = 0; // Tell ConvertString to find proper output buffer size
|
|
hrResult = ConvertString(CP_UTF7, uiDestinationCP, pszSrc, &iLengthOfUTF7,
|
|
&pszDest, &iNumBytesWritten, 0);
|
|
MemFree(pszSrc);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Now write the decoded string to the stream
|
|
hrResult = bstmDestination.Write(pszDest, iNumBytesWritten, NULL);
|
|
MemFree(pszDest);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
pszStartOfLastRun = pszIn; // Set us up for non-UTF7 collection
|
|
continue; // Do not advance pointer, we want to process current char
|
|
} // if end-of-modified-UTF7 run
|
|
} // else
|
|
|
|
// Check for end-of-input
|
|
if ('\0' == cCurrent)
|
|
break; // We're done here
|
|
|
|
// Advance input pointer to next character
|
|
pszIn += 1;
|
|
} // while
|
|
|
|
exit:
|
|
if (SUCCEEDED(hrResult)) {
|
|
hrResult = bstmDestination.HrAcquireStringA(NULL, ppszDestination,
|
|
ACQ_DISPLACE);
|
|
if (SUCCEEDED(hrResult))
|
|
hrResult = S_OK;
|
|
}
|
|
else if (IMAP_MBOXXLATE_VERBATIMOK & m_dwTranslateMboxFlags) {
|
|
// Could not convert UTF-7 to multibyte str. Provide verbatim copy of src
|
|
hrResult = HandleFailedTranslation(fUnicode, FALSE, pszSource, ppszDestination);
|
|
if (SUCCEEDED(hrResult))
|
|
hrResult = IXP_S_IMAP_VERBATIM_MBOX;
|
|
}
|
|
|
|
if (NULL == *ppszDestination && SUCCEEDED(hrResult))
|
|
hrResult = E_OUTOFMEMORY;
|
|
|
|
return hrResult;
|
|
} // ModifiedUTF7ToMultiByte
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: UnicodeToUSASCII
|
|
//
|
|
// Purpose:
|
|
// This function converts a Unicode string to USASCII, allocates a buffer
|
|
// to hold the result and returns the buffer to the caller.
|
|
//
|
|
// Arguments:
|
|
// LPSTR *ppszUSASCII [out] - a pointer to a null-terminated USASCII string
|
|
// is returned here if the function is successful. It is the caller's
|
|
// responsibility to MemFree this buffer.
|
|
// LPCWSTR pwszUnicode [in] - a pointer to the Unicode string to convert.
|
|
// DWORD dwSrcLenInBytes [in] - the length of pwszUnicode in BYTES (NOT in
|
|
// wide chars!).
|
|
// LPDWORD pdwUSASCIILen [out] - the length of ppszUSASCII is returned here.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::UnicodeToUSASCII(LPSTR *ppszUSASCII, LPCWSTR pwszUnicode,
|
|
DWORD dwSrcLenInBytes, LPDWORD pdwUSASCIILen)
|
|
{
|
|
LPSTR pszOutput = NULL;
|
|
BOOL fResult;
|
|
HRESULT hrResult = S_OK;
|
|
LPCWSTR pwszIn;
|
|
LPSTR pszOut;
|
|
int iOutputBufSize;
|
|
DWORD dw;
|
|
|
|
if (NULL == pwszUnicode || NULL == ppszUSASCII) {
|
|
Assert(FALSE);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Allocate the output buffer
|
|
*ppszUSASCII = NULL;
|
|
if (NULL != pdwUSASCIILen)
|
|
*pdwUSASCIILen = 0;
|
|
|
|
iOutputBufSize = (dwSrcLenInBytes/2) + 1;
|
|
fResult = MemAlloc((void **) &pszOutput, iOutputBufSize);
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
// Convert Unicode to ASCII
|
|
pwszIn = pwszUnicode;
|
|
pszOut = pszOutput;
|
|
for (dw = 0; dw < dwSrcLenInBytes; dw += 2) {
|
|
Assert(0 == (*pwszIn & 0xFF80));
|
|
*pszOut = (*pwszIn & 0x00FF);
|
|
|
|
pwszIn += 1;
|
|
pszOut += 1;
|
|
}
|
|
|
|
// Null-terminate the output
|
|
*pszOut = '\0';
|
|
Assert(pszOut - pszOutput + 1 == iOutputBufSize);
|
|
|
|
exit:
|
|
if (SUCCEEDED(hrResult)) {
|
|
*ppszUSASCII = pszOutput;
|
|
if (NULL != pdwUSASCIILen)
|
|
*pdwUSASCIILen = (DWORD) (pszOut - pszOutput);
|
|
}
|
|
|
|
return hrResult;
|
|
} // UnicodeToUSASCII
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ASCIIToUnicode
|
|
//
|
|
// Purpose:
|
|
// This function converts an ASCII string to Unicode, allocates a buffer
|
|
// to hold the result and returns the buffer to the caller.
|
|
//
|
|
// Arguments:
|
|
// LPWSTR *ppwszUnicode [out] - a pointer to a null-terminated Unicode string
|
|
// is returned here if the function is successful. It is the caller's
|
|
// responsibility to MemFree this buffer.
|
|
// LPCSTR pszASCII [in] - a pointer to the ASCII string to convert.
|
|
// DWORD dwSrcLen [in] - the length of pszASCII.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ASCIIToUnicode(LPWSTR *ppwszUnicode, LPCSTR pszASCII,
|
|
DWORD dwSrcLen)
|
|
{
|
|
LPWSTR pwszOutput = NULL;
|
|
BOOL fResult;
|
|
HRESULT hrResult = S_OK;
|
|
LPCSTR pszIn;
|
|
LPWSTR pwszOut;
|
|
int iOutputBufSize;
|
|
DWORD dw;
|
|
|
|
if (NULL == ppwszUnicode || NULL == pszASCII) {
|
|
Assert(FALSE);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Allocate the output buffer
|
|
*ppwszUnicode = NULL;
|
|
iOutputBufSize = (dwSrcLen + 1) * sizeof(WCHAR);
|
|
fResult = MemAlloc((void **) &pwszOutput, iOutputBufSize);
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
// Convert USASCII to Unicode
|
|
pszIn = pszASCII;
|
|
pwszOut = pwszOutput;
|
|
for (dw = 0; dw < dwSrcLen; dw++) {
|
|
*pwszOut = (WCHAR)*pszIn & 0x00FF;
|
|
|
|
pszIn += 1;
|
|
pwszOut += 1;
|
|
}
|
|
|
|
// Null-terminate the output
|
|
*pwszOut = L'\0';
|
|
Assert(pwszOut - pwszOutput + (int)sizeof(WCHAR) == iOutputBufSize);
|
|
|
|
exit:
|
|
if (SUCCEEDED(hrResult))
|
|
*ppwszUnicode = pwszOutput;
|
|
|
|
return hrResult;
|
|
} // ASCIIToUnicode
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: _MultiByteToModifiedUTF7
|
|
//
|
|
// Purpose:
|
|
// Internal form of MultiByteToModifiedUTF7. Checks m_dwTranslateMboxFlags
|
|
// and uses m_uiDefaultCP. All other aspects are identical to
|
|
// MultiByteToModifiedUTF7.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::_MultiByteToModifiedUTF7(LPCSTR pszSource, LPSTR *ppszDestination)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
// Check if we're doing translations
|
|
if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLE) ||
|
|
ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLEIMAP4) &&
|
|
ISFLAGCLEAR(m_dwCapabilityFlags, IMAP_CAPABILITY_IMAP4rev1)) {
|
|
|
|
// No translations! Just copy mailbox name VERBATIM
|
|
if (CP_UNICODE == m_uiDefaultCP)
|
|
*ppszDestination = (LPSTR) PszDupW((LPWSTR)pszSource);
|
|
else
|
|
*ppszDestination = PszDupA(pszSource);
|
|
|
|
if (NULL == *ppszDestination)
|
|
hrResult = E_OUTOFMEMORY;
|
|
else
|
|
hrResult = S_OK;
|
|
|
|
goto exit;
|
|
}
|
|
|
|
hrResult = MultiByteToModifiedUTF7(pszSource, ppszDestination, m_uiDefaultCP, 0);
|
|
|
|
exit:
|
|
return hrResult;
|
|
} // _MultiByteToModifiedUTF7
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: _ModifiedUTF7ToMultiByte
|
|
//
|
|
// Purpose:
|
|
// Internal form of ModifiedUTF7ToMultiByte. Checks m_dwTranslateMboxFlags
|
|
// and uses m_uiDefaultCP. All other aspects are identical to
|
|
// ModifiedUTF7ToMultiByte.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::_ModifiedUTF7ToMultiByte(LPCSTR pszSource, LPSTR *ppszDestination)
|
|
{
|
|
HRESULT hrResult = S_OK;
|
|
|
|
// Check if we're doing translations
|
|
if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLE) ||
|
|
ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLEIMAP4) &&
|
|
ISFLAGCLEAR(m_dwCapabilityFlags, IMAP_CAPABILITY_IMAP4rev1)) {
|
|
|
|
// No translations! Just copy mailbox name VERBATIM
|
|
if (CP_UNICODE == m_uiDefaultCP) {
|
|
hrResult = ASCIIToUnicode((LPWSTR *)ppszDestination, pszSource, lstrlenA(pszSource));
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
}
|
|
else {
|
|
*ppszDestination = PszDupA(pszSource);
|
|
if (NULL == *ppszDestination) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// If we reached this point, we succeeded. Return IXP_S_IMAP_VERBATIM_MBOX for
|
|
// verbatim-capable clients so client can mark mailbox with appropriate attributes
|
|
Assert(S_OK == hrResult); // If not S_OK, old IIMAPTransport clients better be able to deal with it
|
|
if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_VERBATIMOK))
|
|
hrResult = IXP_S_IMAP_VERBATIM_MBOX;
|
|
|
|
goto exit;
|
|
}
|
|
|
|
hrResult = ModifiedUTF7ToMultiByte(pszSource, ppszDestination, m_uiDefaultCP, 0);
|
|
|
|
exit:
|
|
return hrResult;
|
|
} // _ModifiedUTF7ToMultiByte
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ConvertString
|
|
//
|
|
// Purpose:
|
|
// This function allocates a buffer and converts the source string to
|
|
// the target codepage, returning the output buffer. This function also
|
|
// checks to see if the conversion is round-trippable. If not, then a failure
|
|
// result is returned.
|
|
//
|
|
// Arguments:
|
|
// UINT uiSourceCP [in] - codepage of pszSource.
|
|
// UINT uiDestCP [in] - desired codepage of *ppszDest.
|
|
// LPCSTR pszSource [in] - source string to convert to target codepage.
|
|
// int *piSrcLen [in] - caller passes in length of pszSource.
|
|
// LPSTR *ppszDest [out] - if successful, this function returns a pointer
|
|
// to an output buffer containing pszSource translated to uiDestCP.
|
|
// It is the caller's responsibility to MemFree this buffer.
|
|
// int *piDestLen [in/out] - caller passes in maximum expected size of
|
|
// *ppszDest. If caller passes in 0, this function determines the proper
|
|
// size buffer to allocate. If successful, this function returns the
|
|
// length of the output string (which is not necessarily the size of
|
|
// the output buffer).
|
|
// int iOutputExtra [in] - number of extra bytes to allocate in the output
|
|
// buffer. This is useful if the caller wants to append something to
|
|
// the output string.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. Success means that the conversion
|
|
// was roundtrippable, meaning that if you call this function again with
|
|
// *ppszDest as the source, the output will be identical to previous pszSource.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::ConvertString(UINT uiSourceCP, UINT uiDestCP,
|
|
LPCSTR pszSource, int *piSrcLen,
|
|
LPSTR *ppszDest, int *piDestLen,
|
|
int iOutputExtra)
|
|
{
|
|
HRESULT hrResult;
|
|
BOOL fResult;
|
|
int iOutputLen;
|
|
LPSTR pszOutput = NULL;
|
|
|
|
Assert(NULL != pszSource);
|
|
Assert(NULL != piSrcLen);
|
|
Assert(NULL != ppszDest);
|
|
Assert(NULL != piDestLen);
|
|
|
|
// Initialize return values
|
|
*ppszDest = NULL;
|
|
*piDestLen = 0;
|
|
|
|
hrResult = m_pInternational->MLANG_ConvertInetReset();
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
// Find out how big an output buffer is required, if user doesn't supply a size
|
|
if (*piDestLen == 0) {
|
|
hrResult = m_pInternational->MLANG_ConvertInetString(uiSourceCP, uiDestCP,
|
|
pszSource, piSrcLen, NULL, &iOutputLen);
|
|
if (S_OK != hrResult)
|
|
goto exit;
|
|
}
|
|
else
|
|
iOutputLen = *piDestLen;
|
|
|
|
// Allocate the output buffer. Leave room for wide null-term, too
|
|
fResult = MemAlloc((void **)&pszOutput, iOutputLen + iOutputExtra + 2);
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
// Now perform the conversion
|
|
hrResult = m_pInternational->MLANG_ConvertInetString(uiSourceCP, uiDestCP,
|
|
pszSource, piSrcLen, pszOutput, &iOutputLen);
|
|
if (S_OK != hrResult)
|
|
goto exit;
|
|
|
|
// ========================================================*** TAKE OUT after MLANG gets better ***
|
|
// Try the round-trip conversion
|
|
LPSTR pszRoundtrip;
|
|
fResult = MemAlloc((void **)&pszRoundtrip, *piSrcLen + 2);
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
hrResult = m_pInternational->MLANG_ConvertInetReset();
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
int iRoundtripSrc;
|
|
int iRoundtripDest;
|
|
|
|
iRoundtripSrc = iOutputLen;
|
|
iRoundtripDest = *piSrcLen;
|
|
hrResult = m_pInternational->MLANG_ConvertInetString(uiDestCP, uiSourceCP,
|
|
pszOutput, &iRoundtripSrc, pszRoundtrip, &iRoundtripDest);
|
|
if (FAILED(hrResult))
|
|
goto exit;
|
|
|
|
if (iRoundtripDest != *piSrcLen) {
|
|
MemFree(pszRoundtrip);
|
|
hrResult = S_FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
int iRoundtripResult;
|
|
Assert(iRoundtripDest == *piSrcLen);
|
|
if (CP_UNICODE != uiSourceCP)
|
|
iRoundtripResult = StrCmpNA(pszRoundtrip, pszSource, iRoundtripDest);
|
|
else
|
|
iRoundtripResult = StrCmpNW((LPWSTR)pszRoundtrip, (LPCWSTR)pszSource, iRoundtripDest);
|
|
|
|
MemFree(pszRoundtrip);
|
|
if (0 != iRoundtripResult)
|
|
hrResult = S_FALSE;
|
|
else
|
|
Assert(S_OK == hrResult);
|
|
|
|
// ========================================================*** TAKE OUT after MLANG gets better ***
|
|
|
|
exit:
|
|
if (S_OK == hrResult) {
|
|
*ppszDest = pszOutput;
|
|
*piDestLen = iOutputLen;
|
|
}
|
|
else {
|
|
if (SUCCEEDED(hrResult))
|
|
// One or more chars not convertable. We're not round-trippable so we must fail
|
|
hrResult = E_FAIL;
|
|
|
|
if (NULL != pszOutput)
|
|
MemFree(pszOutput);
|
|
}
|
|
|
|
return hrResult;
|
|
} // ConvertString
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: HandleFailedTranslation
|
|
//
|
|
// Purpose:
|
|
// In case we cannot translate a mailbox name from modified UTF-7 to
|
|
// the desired codepage (we may not have the codepage, for instance), we
|
|
// provide a duplicate of the modified UTF-7 mailbox name. This function
|
|
// allows the caller to ignore whether target codepage is Unicode or not.
|
|
//
|
|
// Arguments:
|
|
// BOOL fUnicode [in] - If fToUTF7 is TRUE, then this argument indicates
|
|
// whether pszSource points to a Unicode string or not. If fToUTF7 is
|
|
// FALSE, this arg indicates whether *ppszDest should be in Unicode or not.
|
|
// BOOL fToUTF7 [in] - TRUE if we are converting to UTF7, FALSE if we are
|
|
// converting from UTF7.
|
|
// LPCSTR pszSource [in] - pointer to source string.
|
|
// LPSTR *ppszDest [in] - if sucessful, this function returns a pointer
|
|
// to an output buffer containing a duplicate of pszSource (converted
|
|
// to/from Unicode where necessary). It is the caller's responsibility
|
|
// to MemFree this buffer.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT CImap4Agent::HandleFailedTranslation(BOOL fUnicode, BOOL fToUTF7,
|
|
LPCSTR pszSource, LPSTR *ppszDest)
|
|
{
|
|
int i;
|
|
int iOutputStep;
|
|
int iInputStep;
|
|
int iSourceLen;
|
|
int iOutputBufSize;
|
|
BOOL fResult;
|
|
LPSTR pszOutput = NULL;
|
|
HRESULT hrResult = S_OK;
|
|
LPCSTR pszIn;
|
|
LPSTR pszOut;
|
|
|
|
Assert(m_lRefCount > 0);
|
|
Assert(ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_VERBATIMOK));
|
|
|
|
// Calculate length of source, size of output buffer
|
|
if (fToUTF7) {
|
|
// Going to UTF7, so output is USASCII
|
|
if (fUnicode) {
|
|
iInputStep = sizeof(WCHAR);
|
|
iSourceLen = lstrlenW((LPCWSTR)pszSource);
|
|
}
|
|
else {
|
|
iInputStep = sizeof(char);
|
|
iSourceLen = lstrlenA(pszSource);
|
|
}
|
|
|
|
iOutputStep = sizeof(char);
|
|
iOutputBufSize = iSourceLen + sizeof(char); // Room for null-term
|
|
}
|
|
else {
|
|
// Coming from UTF7, so input is USASCII
|
|
iSourceLen = lstrlenA(pszSource);
|
|
iInputStep = sizeof(char);
|
|
if (fUnicode) {
|
|
iOutputStep = sizeof(WCHAR);
|
|
iOutputBufSize = (iSourceLen + 1) * sizeof(WCHAR); // Room for wide null-term
|
|
}
|
|
else {
|
|
iOutputStep = sizeof(char);
|
|
iOutputBufSize = iSourceLen + sizeof(char); // Room for null-term
|
|
}
|
|
}
|
|
|
|
// Allocate output buffer
|
|
fResult = MemAlloc((void **)&pszOutput, iOutputBufSize);
|
|
if (FALSE == fResult) {
|
|
hrResult = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
// Copy input to output
|
|
pszIn = pszSource;
|
|
pszOut = pszOutput;
|
|
for (i = 0; i < iSourceLen; i++) {
|
|
char c;
|
|
|
|
// Convert input character to USASCII
|
|
if (FALSE == fUnicode || FALSE == fToUTF7)
|
|
c = *pszIn; // Input is already USASCII
|
|
else
|
|
c = *((LPWSTR)pszIn) & 0x00FF; // Convert Unicode to USASCII (too bad if it isn't)
|
|
|
|
// Write character to output
|
|
SetUSASCIIChar(FALSE == fToUTF7 && fUnicode, pszOut, c);
|
|
|
|
// Advance pointers
|
|
pszIn += iInputStep;
|
|
pszOut += iOutputStep;
|
|
}
|
|
|
|
// Null-terminate the output
|
|
SetUSASCIIChar(FALSE == fToUTF7 && fUnicode, pszOut, '\0');
|
|
|
|
exit:
|
|
if (SUCCEEDED(hrResult))
|
|
*ppszDest = pszOutput;
|
|
else if (NULL != pszOutput)
|
|
MemFree(pszOutput);
|
|
|
|
return hrResult;
|
|
} // HandleFailedTranslation
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnIMAPResponse
|
|
//
|
|
// Purpose:
|
|
// This function dispatches a IIMAPCallback::OnResponse call. The reason
|
|
// to use this function instead of calling directly is watchdog timers: the
|
|
// watchdog timers should be disabled before the call in case the callback
|
|
// puts up some UI, and the watchdog timers should be restarted if they're
|
|
// needed after the callback function returns.
|
|
//
|
|
// Arguments:
|
|
// IIMAPCallback *pCBHandler [in] - a pointer to the IIMAPCallback
|
|
// interface whose OnResponse we should call.
|
|
// IMAP_RESPONSE *pirIMAPResponse [in] - a pointer to the IMAP_RESPONSE
|
|
// structure to send with the IIMAPCallback::OnResponse call.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnIMAPResponse(IIMAPCallback *pCBHandler,
|
|
IMAP_RESPONSE *pirIMAPResponse)
|
|
{
|
|
Assert(NULL != pirIMAPResponse);
|
|
|
|
if (NULL == pCBHandler)
|
|
return; // We can't do a damned thing (this can happen due to HandsOffCallback)
|
|
|
|
// Suspend watchdog for the duration of this callback
|
|
LeaveBusy();
|
|
|
|
pCBHandler->OnResponse(pirIMAPResponse);
|
|
|
|
// Re-awaken the watchdog only if we need him
|
|
if (FALSE == m_fBusy &&
|
|
(NULL != m_piciPendingList || (NULL != m_piciCmdInSending &&
|
|
icIDLE_COMMAND != m_piciCmdInSending->icCommandID))) {
|
|
HRESULT hrResult;
|
|
|
|
hrResult = HrEnterBusy();
|
|
Assert(SUCCEEDED(hrResult));
|
|
}
|
|
} // OnIMAPResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FreeFetchResponse
|
|
//
|
|
// Purpose:
|
|
// This function frees all the allocated data found in a
|
|
// FETCH_CMD_RESULTS_EX structure.
|
|
//
|
|
// Arguments:
|
|
// FETCH_CMD_RESULTS_EX *pcreFreeMe [in] - pointer to the structure to
|
|
// free.
|
|
//***************************************************************************
|
|
void CImap4Agent::FreeFetchResponse(FETCH_CMD_RESULTS_EX *pcreFreeMe)
|
|
{
|
|
SafeMemFree(pcreFreeMe->pszENVSubject);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVFrom);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVSender);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVReplyTo);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVTo);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVCc);
|
|
FreeIMAPAddresses(pcreFreeMe->piaENVBcc);
|
|
SafeMemFree(pcreFreeMe->pszENVInReplyTo);
|
|
SafeMemFree(pcreFreeMe->pszENVMessageID);
|
|
} // FreeFetchResponse
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: FreeIMAPAddresses
|
|
//
|
|
// Purpose:
|
|
// This function frees all the allocated data found in a chain of IMAPADDR
|
|
// structures.
|
|
//
|
|
// Arguments:
|
|
// IMAPADDR *piaFreeMe [in] - pointer to the chain of IMAP addresses to free.
|
|
//***************************************************************************
|
|
void CImap4Agent::FreeIMAPAddresses(IMAPADDR *piaFreeMe)
|
|
{
|
|
while (NULL != piaFreeMe)
|
|
{
|
|
IMAPADDR *piaFreeMeToo;
|
|
|
|
SafeMemFree(piaFreeMe->pszName);
|
|
SafeMemFree(piaFreeMe->pszADL);
|
|
SafeMemFree(piaFreeMe->pszMailbox);
|
|
SafeMemFree(piaFreeMe->pszHost);
|
|
|
|
// Advance pointer, free structure
|
|
piaFreeMeToo = piaFreeMe;
|
|
piaFreeMe = piaFreeMe->pNext;
|
|
MemFree(piaFreeMeToo);
|
|
}
|
|
} // FreeIMAPAddresses
|
|
|
|
|
|
|
|
//===========================================================================
|
|
// IInternetTransport Abstract Functions
|
|
//===========================================================================
|
|
|
|
//***************************************************************************
|
|
// Function: GetServerInfo
|
|
//
|
|
// Purpose:
|
|
// This function copies the module's INETSERVER structure into the given
|
|
// output buffer.
|
|
//
|
|
// Arguments:
|
|
// LPINETSERVER pInetServer [out] - if successful, the function copies
|
|
// the module's INETSERVER structure here.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::GetServerInfo(LPINETSERVER pInetServer)
|
|
{
|
|
return CIxpBase::GetServerInfo(pInetServer);
|
|
} // GetServerInfo
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetIXPType
|
|
//
|
|
// Purpose:
|
|
// This function identifies what type of transport this is.
|
|
//
|
|
// Returns:
|
|
// IXP_IMAP for this class.
|
|
//***************************************************************************
|
|
IXPTYPE STDMETHODCALLTYPE CImap4Agent::GetIXPType(void)
|
|
{
|
|
return CIxpBase::GetIXPType();
|
|
} // GetIXPType
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: IsState
|
|
//
|
|
// Purpose:
|
|
// This function allows a caller to query about the state of the transport
|
|
// interface.
|
|
//
|
|
// Arguments:
|
|
// IXPISSTATE isstate [in] - one of the specified queries defined in
|
|
// imnxport.idl/imnxport.h (eg, IXP_IS_CONNECTED).
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure. If successful, this function
|
|
// returns S_OK to indicate that the transport is in the specified state,
|
|
// and S_FALSE to indicate that the transport is not in the given state.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::IsState(IXPISSTATE isstate)
|
|
{
|
|
return CIxpBase::IsState(isstate);
|
|
} // IsState
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: InetServerFromAccount
|
|
//
|
|
// Purpose:
|
|
// This function fills the given INETSERVER structure using the given
|
|
// IImnAccount interface.
|
|
//
|
|
// Arguments:
|
|
// IImnAccount *pAccount [in] - pointer to an IImnAccount interface which
|
|
// the user would like to retrieve information for.
|
|
// LPINETSERVER pInetServer [out] - if successful, the function fills the
|
|
// given INETSERVER structure with information from pAccount.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::InetServerFromAccount(IImnAccount *pAccount,
|
|
LPINETSERVER pInetServer)
|
|
{
|
|
return CIxpBase::InetServerFromAccount(pAccount, pInetServer);
|
|
} // InetServerFromAccount
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: GetStatus
|
|
//
|
|
// Purpose:
|
|
// This function returns the current status of the transport.
|
|
//
|
|
// Arguments:
|
|
// IXPSTATUS *pCurrentStatus [out] - returns current status of transport.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::GetStatus(IXPSTATUS *pCurrentStatus)
|
|
{
|
|
return CIxpBase::GetStatus(pCurrentStatus);
|
|
} // GetStatus
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SetDefaultCP
|
|
//
|
|
// Purpose:
|
|
// This function allows the caller to tell IIMAPTransport what codepage to
|
|
// use for IMAP mailbox names. After calling this function, all mailbox names
|
|
// submitted to IIMAPTransport will be translated from the default codepage,
|
|
// and all mailbox names returned from the server will be translated to
|
|
// the default codepage before being returned via IIMAPCallback.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwTranslateFlags [in] - enables/disables automatic translation to
|
|
// and from default codepage and IMAP-modified UTF-7. If disabled, caller
|
|
// wishes all mailbox names to be passed verbatim to/from IMAP server.
|
|
// Note that by default we translate for IMAP4 servers, even with its
|
|
// round-trippability problems, because this is how we used to do it
|
|
// in the past.
|
|
// UINT uiCodePage [in] - the default codepage to use for translations.
|
|
// By default this value is the CP returned by GetACP().
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::SetDefaultCP(DWORD dwTranslateFlags,
|
|
UINT uiCodePage)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
if (ISFLAGCLEAR(dwTranslateFlags, IMAP_MBOXXLATE_RETAINCP))
|
|
m_uiDefaultCP = uiCodePage;
|
|
|
|
dwTranslateFlags &= ~(IMAP_MBOXXLATE_RETAINCP);
|
|
m_dwTranslateMboxFlags = dwTranslateFlags;
|
|
|
|
return S_OK;
|
|
} // SetDefaultCP
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: SetIdleMode
|
|
//
|
|
// Purpose:
|
|
// The IMAP IDLE extension allows the server to unilaterally report changes
|
|
// to the currently selected mailbox: new email, flag updates, and message
|
|
// expunges. IIMAPTransport always enters IDLE mode when no IMAP commands
|
|
// are pending, but it turns out that this can result in unnecessary
|
|
// entry and exit of IDLE mode when the caller tries to sequence IMAP commands.
|
|
// This function allows the caller to disable the use of the IDLE extension.
|
|
//
|
|
// Arguments:
|
|
// DWORD dwIdleFlags [in] - enables or disables the use of the IDLE extension.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::SetIdleMode(DWORD dwIdleFlags)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
return E_NOTIMPL;
|
|
} // SetIdleMode
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: EnableFetchEx
|
|
//
|
|
// Purpose:
|
|
// IIMAPTransport only understood a subset of FETCH response tags. Notable
|
|
// omissions included ENVELOPE and BODYSTRUCTURE. Calling this function
|
|
// changes the behaviour of IIMAPTransport::Fetch. Instead of returning
|
|
// FETCH responses via IIMAPCallback::OnResponse(irtUPDATE_MESSAGE),
|
|
// the FETCH response is returned via OnResponse(irtUPDATE_MESSAGE_EX).
|
|
// Other FETCH-related responses remain unaffected (eg, irtFETCH_BODY).
|
|
//
|
|
// Arguments:
|
|
// DWORD dwFetchExFlags [in] - enables or disables FETCH extensions.
|
|
//
|
|
// Returns:
|
|
// HRESULT indicating success or failure.
|
|
//***************************************************************************
|
|
HRESULT STDMETHODCALLTYPE CImap4Agent::EnableFetchEx(DWORD dwFetchExFlags)
|
|
{
|
|
Assert(m_lRefCount > 0);
|
|
|
|
m_dwFetchFlags = dwFetchExFlags;
|
|
return S_OK;
|
|
} // EnableFetchEx
|
|
|
|
|
|
|
|
//===========================================================================
|
|
// CIxpBase Abstract Functions
|
|
//===========================================================================
|
|
|
|
//***************************************************************************
|
|
// Function: OnDisconnected
|
|
//
|
|
// Purpose:
|
|
// This function calls FreeAllData to deallocate the structures which are
|
|
// no longer needed when we are disconnected. It then calls
|
|
// CIxpBase::OnDisconnected which updates the user's status.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnDisconnected(void)
|
|
{
|
|
FreeAllData(IXP_E_CONNECTION_DROPPED);
|
|
CIxpBase::OnDisconnected();
|
|
} // OnDisconnected
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: ResetBase
|
|
//
|
|
// Purpose:
|
|
// This function resets the class to a non-connected state by deallocating
|
|
// the send and receive queues, and the MsgSeqNumToUID table.
|
|
//***************************************************************************
|
|
void CImap4Agent::ResetBase(void)
|
|
{
|
|
FreeAllData(IXP_E_NOT_CONNECTED);
|
|
} // ResetBase
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: DoQuit
|
|
//
|
|
// Purpose:
|
|
// This function sends a "LOGOUT" command to the IMAP server.
|
|
//***************************************************************************
|
|
void CImap4Agent::DoQuit(void)
|
|
{
|
|
HRESULT hrResult;
|
|
|
|
hrResult = NoArgCommand("LOGOUT", icLOGOUT_COMMAND, ssNonAuthenticated, 0, 0,
|
|
DEFAULT_CBHANDLER);
|
|
Assert(SUCCEEDED(hrResult));
|
|
} // DoQuit
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnEnterBusy
|
|
//
|
|
// Purpose:
|
|
// This function does nothing at the current time.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnEnterBusy(void)
|
|
{
|
|
// Do nothing
|
|
} // OnEnterBusy
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
// Function: OnLeaveBusy
|
|
//
|
|
// Purpose:
|
|
// This function does nothing at the current time.
|
|
//***************************************************************************
|
|
void CImap4Agent::OnLeaveBusy(void)
|
|
{
|
|
// Do nothing
|
|
} // OnLeaveBusy
|