Leaked source code of windows server 2003
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

//***************************************************************************
// 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 = &ap;
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