//*************************************************************************** // 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 #include "IMAP4.h" #include "range.h" #include "dllmain.h" #include "resource.h" #include "mimeole.h" #include #include "strconst.h" #include "demand.h" // I chose the IInternetTransport from IIMAPTransport instead // of CIxpBase, because I override some of CIxpBase's IInternetTransport // implementations, and I want CImap4Agent's versions to take priority. #define THIS_IInternetTransport ((IInternetTransport *) (IIMAPTransport *) this) //--------------------------------------------------------------------------- // Module Constants //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // Module Constants //--------------------------------------------------------------------------- // *** Stolen from msgout.cpp! Find out how we can SHARE *** // Assert(FALSE); // Placeholder // The following is used to allow us to output dates in IMAP-compliant fashion static LPSTR lpszMonthsOfTheYear[] = { "Filler", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; const int TAG_BUFSIZE = NUM_TAG_CHARS + 1; const int MAX_RESOURCESTRING = 512; // IMAP Stuff const char cCOMMAND_CONTINUATION_PREFIX = '+'; const char cUNTAGGED_RESPONSE_PREFIX = '*'; const char cSPACE = ' '; const char c_szIMAP_MSG_ANSWERED[] = "Answered"; const char c_szIMAP_MSG_FLAGGED[] = "Flagged"; const char c_szIMAP_MSG_DELETED[] = "Deleted"; const char c_szIMAP_MSG_DRAFT[] = "Draft"; const char c_szIMAP_MSG_SEEN[] = "Seen"; const char c_szDONE[] = "DONE\r\n"; // *** Unless you can guarantee that g_szSPACE and c_szCRLF stay // *** US-ASCII, I'll use these. Assert(FALSE); (placeholder) // const char c_szCRLF[] = "\r\n"; // const char g_szSpace[] = " "; const boolean TAGGED = TRUE; const boolean UNTAGGED = FALSE; const BOOL fFREE_BODY_TAG = TRUE; const BOOL fDONT_FREE_BODY_TAG = FALSE; const BOOL tamNEXT_AUTH_METHOD = TRUE; const BOOL tamCURRENT_AUTH_METHOD = FALSE; const BOOL rcASTRING_ARG = TRUE; const BOOL rcNOT_ASTRING_ARG = FALSE; // For use with SendCmdLine const DWORD sclAPPEND_TO_END = 0x00000000; // This option happens by default const DWORD sclINSERT_BEFORE_PAUSE = 0x00000001; const DWORD sclAPPEND_CRLF = 0x00000002; const DWORD dwLITERAL_THRESHOLD = 128; // On the conservative side const MBOX_MSGCOUNT mcMsgCount_INIT = {FALSE, 0L, FALSE, 0L, FALSE, 0L}; const FETCH_BODY_PART FetchBodyPart_INIT = {0, NULL, 0, 0, 0, FALSE, NULL, 0, 0}; const AUTH_STATUS AuthStatus_INIT = {asUNINITIALIZED, FALSE, 0, 0, {0}, {0}, NULL, 0}; //--------------------------------------------------------------------------- // Functions //--------------------------------------------------------------------------- //*************************************************************************** // Function: CImap4Agent (Constructor) //*************************************************************************** CImap4Agent::CImap4Agent (void) : CIxpBase(IXP_IMAP) { DOUT("CImap4Agent - CONSTRUCTOR"); // Initialize module variables m_ssServerState = ssNotConnected; m_dwCapabilityFlags = 0; *m_szLastResponseText = '\0'; DllAddRef(); m_lRefCount = 1; m_pCBHandler = NULL; m_irsState = irsUNINITIALIZED; m_bFreeToSend = TRUE; m_fIDLE = FALSE; m_ilqRecvQueue = ImapLinefragQueue_INIT; InitializeCriticalSection(&m_csTag); InitializeCriticalSection(&m_csSendQueue); InitializeCriticalSection(&m_csPendingList); m_pilfLiteralInProgress = NULL; m_dwLiteralInProgressBytesLeft = 0; m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT; m_dwAppendStreamUploaded = 0; m_dwAppendStreamTotal = 0; m_bCurrentMboxReadOnly = TRUE; m_piciSendQueue = NULL; m_piciPendingList = NULL; m_piciCmdInSending = NULL; m_pInternational = NULL; m_dwTranslateMboxFlags = IMAP_MBOXXLATE_DEFAULT; m_uiDefaultCP = GetACP(); // Must be default CP because we shipped like this m_asAuthStatus = AuthStatus_INIT; m_pdwMsgSeqNumToUID = NULL; m_dwSizeOfMsgSeqNumToUID = 0; m_dwHighestMsgSeqNum = 0; m_dwFetchFlags = 0; } // CImap4Agent //*************************************************************************** // Function: ~CImap4Agent (Destructor) //*************************************************************************** CImap4Agent::~CImap4Agent(void) { DOUT("CImap4Agent - DESTRUCTOR"); Assert(0 == m_lRefCount); DropConnection(); // Ignore return result, since there's nothing we can do FreeAllData(E_FAIL); // General failure result, if cmds pending while destructor invoked DeleteCriticalSection(&m_csTag); DeleteCriticalSection(&m_csSendQueue); DeleteCriticalSection(&m_csPendingList); if (NULL != m_pInternational) m_pInternational->Release(); if (NULL != m_pCBHandler) m_pCBHandler->Release(); DllRelease(); } // ~CImap4Agent //*************************************************************************** // Function: QueryInterface // // Purpose: // Read the Win32SDK OLE Programming References (Interfaces) about the // IUnknown::QueryInterface function for details. This function returns a // pointer to the requested interface. // // Arguments: // REFIID iid [in] - an IID identifying the interface to return. // void **ppvObject [out] - if successful, this function returns a pointer // to the requested interface in this argument. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::QueryInterface(REFIID iid, void **ppvObject) { HRESULT hrResult; Assert(m_lRefCount > 0); Assert(NULL != ppvObject); // Init variables, check the arguments hrResult = E_NOINTERFACE; if (NULL == ppvObject) { hrResult = E_INVALIDARG; goto exit; } *ppvObject = NULL; // Find a ptr to the interface if (IID_IUnknown == iid) { // Choose the IIMAPTransport path to IUnknown over the other 3 paths // (all through CIxpBase) because this guarantees that CImap4Agent // provides the IUnknown implementation. *ppvObject = (IUnknown *) (IIMAPTransport *) this; ((IUnknown *) (IIMAPTransport *) this)->AddRef(); } if (IID_IInternetTransport == iid) { *ppvObject = THIS_IInternetTransport; (THIS_IInternetTransport)->AddRef(); } if (IID_IIMAPTransport == iid) { *ppvObject = (IIMAPTransport *) this; ((IIMAPTransport *) this)->AddRef(); } if (IID_IIMAPTransport2 == iid) { *ppvObject = (IIMAPTransport2 *) this; ((IIMAPTransport2 *) this)->AddRef(); } // Return success if we managed to snag an interface if (NULL != *ppvObject) hrResult = S_OK; exit: return hrResult; } // QueryInterface //*************************************************************************** // Function: AddRef // // Purpose: // This function should be called whenever someone makes a copy of a // pointer to this object. It bumps the reference count so that we know // there is one more pointer to this object, and thus we need one more // release before we delete ourselves. // // Returns: // A ULONG representing the current reference count. Although technically // our reference count is signed, we should never return a negative number, // anyways. //*************************************************************************** ULONG STDMETHODCALLTYPE CImap4Agent::AddRef(void) { Assert(m_lRefCount > 0); m_lRefCount += 1; DOUT ("CImap4Agent::AddRef, returned Ref Count=%ld", m_lRefCount); return m_lRefCount; } // AddRef //*************************************************************************** // Function: Release // // Purpose: // This function should be called when a pointer to this object is to // go out of commission. It knocks the reference count down by one, and // automatically deletes the object if we see that nobody has a pointer // to this object. // // Returns: // A ULONG representing the current reference count. Although technically // our reference count is signed, we should never return a negative number, // anyways. //*************************************************************************** ULONG STDMETHODCALLTYPE CImap4Agent::Release(void) { Assert(m_lRefCount > 0); m_lRefCount -= 1; DOUT("CImap4Agent::Release, returned Ref Count = %ld", m_lRefCount); if (0 == m_lRefCount) { delete this; return 0; } else return m_lRefCount; } // Release //*************************************************************************** // Function: InitNew // // Purpose: // This function initializes the CImap4Agent class. This function // must be the next function called after instantiating the CImap4Agent class. // // Arguments: // LPSTR pszLogFilePath [in] - path to a log file (where all input and // output is logged), if the caller wishes to log IMAP transactions. // IIMAPCallback *pCBHandler [in] - pointer to a IIMAPCallback object. // This object allows the CImap4Agent class to report all IMAP response // results to its user. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::InitNew(LPSTR pszLogFilePath, IIMAPCallback *pCBHandler) { HRESULT hrResult; Assert(m_lRefCount > 0); Assert(ssNotConnected == m_ssServerState); Assert(irsUNINITIALIZED == m_irsState); Assert(NULL != pCBHandler); pCBHandler->AddRef(); m_pCBHandler = pCBHandler; m_irsState = irsNOT_CONNECTED; hrResult = MimeOleGetInternat(&m_pInternational); if (FAILED(hrResult)) return hrResult; return CIxpBase::OnInitNew("IMAP", pszLogFilePath, FILE_SHARE_READ, (ITransportCallback *)pCBHandler); } // InitNew //*************************************************************************** // Function: SetDefaultCBHandler // // Purpose: This function changes the current default IIMAPCallback handler // to the given one. // // Arguments: // IIMAPCallback *pCBHandler [in] - a pointer to the new callback handler. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::SetDefaultCBHandler(IIMAPCallback *pCBHandler) { Assert(NULL != pCBHandler); if (NULL == pCBHandler) return E_INVALIDARG; if (NULL != m_pCBHandler) m_pCBHandler->Release(); if (NULL != m_pCallback) m_pCallback->Release(); m_pCBHandler = pCBHandler; m_pCBHandler->AddRef(); m_pCallback = pCBHandler; m_pCallback->AddRef(); return S_OK; } // SetDefaultCBHandler //*************************************************************************** // Function: SetWindow // // Purpose: // This function creates the current window handle for async winsock process. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::SetWindow(void) { Assert(NULL != m_pSocket); return m_pSocket->SetWindow(); } // SetWindow //*************************************************************************** // Function: ResetWindow // // Purpose: // This function closes the current window handle for async winsock process. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::ResetWindow(void) { Assert(NULL != m_pSocket); return m_pSocket->ResetWindow(); } // ResetWindow //*************************************************************************** // Function: Connect // // Purpose: // This function is called to establish a connection with the IMAP server, // get its capabilities, and to authenticate the user. // // Arguments: // See explanation in imnxport.idl. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::Connect(LPINETSERVER pInetServer, boolean fAuthenticate, boolean fCommandLogging) { HRESULT hrResult; Assert(m_lRefCount > 0); Assert(ssAuthenticated > m_ssServerState); Assert(irsUNINITIALIZED < m_irsState); // We do not accept all combinations of argument: the caller cannot // perform his own authentication, and thus we MUST be responsible for // this. Even if PREAUTH is expected, we expect fAuthenticate to be TRUE. if (FALSE == fAuthenticate) { AssertSz(FALSE, "Current IIMAPTransport interface requires that fAuthenticate be TRUE."); return E_FAIL; } // Neither can we call the OnCommand callback if (fCommandLogging) { AssertSz(FALSE, "Current IIMAPTransport interface requires that fCommandLogging be FALSE."); return E_FAIL; } // Does user want us to always prompt for his password? Prompt him here to avoid // inactivity timeouts while the prompt is up. Do not prompt if password supplied. if (ISFLAGSET(pInetServer->dwFlags, ISF_ALWAYSPROMPTFORPASSWORD) && '\0' == pInetServer->szPassword[0]) { if (NULL != m_pCallback) hrResult = m_pCallback->OnLogonPrompt(pInetServer, THIS_IInternetTransport); if (NULL == m_pCallback || S_OK != hrResult) return IXP_E_USER_CANCEL; } // If we reach this point, we need to establish a connection to IMAP server Assert(ssNotConnected == m_ssServerState); Assert(irsNOT_CONNECTED == m_irsState); hrResult = CIxpBase::Connect(pInetServer, fAuthenticate, fCommandLogging); if (SUCCEEDED(hrResult)) { m_irsState = irsSVR_GREETING; m_ssServerState = ssConnecting; } return hrResult; } // Connect //*************************************************************************** // Function: ReLoginUser // // Purpose: // This function is called to re-attempt user authentication after a // failed attempt. It calls ITransportCallback::OnLogonPrompt to allow // the user to provide the correct logon information. //*************************************************************************** void CImap4Agent::ReLoginUser(void) { HRESULT hrResult; char szFailureText[MAX_RESOURCESTRING]; AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!"); if (NULL == m_pCallback) { // We can't do a damned thing, drop connection (this can happen due to HandsOffCallback) DropConnection(); return; } // Init variables szFailureText[0] = '\0'; // First, put us in IXP_AUTHRETRY mode so that OnStatus is not called // for changes to the connection status OnStatus(IXP_AUTHRETRY); // OK, connection status is no longer being reported to the user // Ask the user for his stinking password hrResult = m_pCallback->OnLogonPrompt(&m_rServer, THIS_IInternetTransport); if (FAILED(hrResult) || S_FALSE == hrResult) { AssertSz(SUCCEEDED(hrResult), "OnLogonPrompt is supposed to return S_OK or S_FALSE!"); DropConnection(); goto exit; } // If we've reached this point, user hit the "OK" button // Check if we're still connected to the IMAP server if (irsNOT_CONNECTED < m_irsState) { // Still connected! Just try to authenticate LoginUser(); } else { // Connect to server. We'll authenticate after connection established hrResult = Connect(&m_rServer, (boolean) !!m_fConnectAuth, (boolean) !!m_fCommandLogging); if (FAILED(hrResult)) LoadString(g_hLocRes, idsConnectError, szFailureText, ARRAYSIZE(szFailureText)); } exit: if (FAILED(hrResult)) { // Terminate login procedure and notify user OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE); } } // ReLoginUser //*************************************************************************** // Function: Disconnect // // Purpose: // This function issues a LOGOUT command to the IMAP server and waits for // the server to process the LOGOUT command before dropping the connection. // This allows any currently executing commands to complete their execution. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::Disconnect(void) { return CIxpBase::Disconnect(); } // Disconnect //*************************************************************************** // Function: DropConnection // // Purpose: // This function issues a LOGOUT command to the IMAP server (if we // currently have nothing in the send queue), then drops the connection // before logout command completes. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT STDMETHODCALLTYPE CImap4Agent::DropConnection(void) { Assert(m_lRefCount >= 0); // This function is called during destruction // You have to be connected to send a LOGOUT: ignore authorization states if (IXP_CONNECTED != m_status) goto exit; // Just close the CAsyncConn class // We send a logout command IF WE CAN, just as a courtesy. Our main goal // is to drop the connection, NOW. // If no commands in our send queue, send Logout command. Note that this // is no guarantee that CASyncConn is idle, but at least there's a chance if (NULL == m_piciCmdInSending || (m_fIDLE && icIDLE_COMMAND == m_piciCmdInSending->icCommandID && iltPAUSE == m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment->iltFragmentType)) { HRESULT hrLogoutResult; const char cszLogoutCmd[] = "ZZZZ LOGOUT\r\n"; char sz[ARRAYSIZE(cszLogoutCmd) + ARRAYSIZE(c_szDONE)]; // Bigger than I need, but who cares int iNumBytesSent, iStrLen; // Construct logout or done+logout string if (m_fIDLE) { StrCpyN(sz, c_szDONE, ARRAYSIZE(sz)); StrCpyN(sz + ARRAYSIZE(c_szDONE) - 1, cszLogoutCmd, (ARRAYSIZE(sz) - ARRAYSIZE(c_szDONE) + 1)); iStrLen = ARRAYSIZE(c_szDONE) + ARRAYSIZE(cszLogoutCmd) - 2; } else { StrCpyN(sz, cszLogoutCmd, ARRAYSIZE(sz)); iStrLen = ARRAYSIZE(cszLogoutCmd) - 1; } Assert(iStrLen == lstrlen(sz)); hrLogoutResult = m_pSocket->SendBytes(sz, iStrLen, &iNumBytesSent); Assert(SUCCEEDED(hrLogoutResult)); Assert(iNumBytesSent == iStrLen); if (m_pLogFile) m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT sent"); } else { if (m_pLogFile) m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT not sent"); } // else exit: // Drop our connection, with status indication return CIxpBase::DropConnection(); } // DropConnection //*************************************************************************** // Function: ProcessServerGreeting // // Purpose: // This function is invoked when the receiver state machine is in // irsSVR_GREETING and a response line is received from the server. This // function takes a server greeting line (issued immediately when a // connection is established with the IMAP server) and parses it to // determine if: a) We are pre-authorized, and therefore do not need to // login, b) We have been refused the connection, or c) We must login. // // Arguments: // char *pszResponseLine [in] - the server greeting issued upon connection. // DWORD dwNumBytesReceived [in] - length of pszResponseLine string. //*************************************************************************** void CImap4Agent::ProcessServerGreeting(char *pszResponseLine, DWORD dwNumBytesReceived) { HRESULT hrResult; IMAP_RESPONSE_ID irResult; char szFailureText[MAX_RESOURCESTRING]; BOOL bUseLastResponse; Assert(m_lRefCount > 0); Assert(NULL != pszResponseLine); // Initialize variables szFailureText[0] = '\0'; hrResult = E_FAIL; bUseLastResponse = FALSE; // Whatever happens next, we no longer expect server greeting - change state m_irsState = irsIDLE; // We have some kind of server response, so leave the busy state AssertSz(m_fBusy, "Check your logic: we should be busy until we get svr greeting!"); LeaveBusy(); // Server response is either OK, BYE or PREAUTH - find out which CheckForCompleteResponse(pszResponseLine, dwNumBytesReceived, &irResult); // Even if above fn fails, irResult should be valid (eg, irNONE) switch (irResult) { case irPREAUTH_RESPONSE: // We were pre-authorized by the server! Login is complete. // Send capability command Assert(ssAuthenticated == m_ssServerState); hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND, ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER); break; case irBYE_RESPONSE: // Server blew us off (ie, issued BYE)! Login failed. Assert(ssNotConnected == m_ssServerState); hrResult = IXP_E_IMAP_CONNECTION_REFUSED; LoadString(g_hLocRes, idsSvrRefusesConnection, szFailureText, ARRAYSIZE(szFailureText)); bUseLastResponse = TRUE; break; case irOK_RESPONSE: { // Server response was "OK". We need to log in. Assert(ssConnecting == m_ssServerState); m_ssServerState = ssNonAuthenticated; // Send capability command - on its completion, we'll authenticate hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND, ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER); break; } // case hrIMAP_S_OK_RESPONSE default: // Has server gone absolutely LOOPY? AssertSz(FALSE, "What kind of server greeting is this?"); hrResult = E_FAIL; LoadString(g_hLocRes, idsUnknownIMAPGreeting, szFailureText, ARRAYSIZE(szFailureText)); bUseLastResponse = TRUE; break; } // switch(hrResult) if (FAILED(hrResult)) { if ('\0' == szFailureText[0]) { LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText, ARRAYSIZE(szFailureText)); } // Terminate login procedure and notify user OnIMAPError(hrResult, szFailureText, bUseLastResponse); DropConnection(); } } // ProcessServerGreeting //*************************************************************************** // Function: LoginUser // // Purpose: // This function is responsible for kickstarting the login process. // // Returns: // Nothing, because any errors are reported via CmdCompletionNotification // callback to the user. Any errors encountered in this function will be // encountered during command transmittal, and so there's nothing further we // can do... may as well end the login process here. //*************************************************************************** void CImap4Agent::LoginUser(void) { HRESULT hrResult; Assert(m_lRefCount > 0); Assert(ssNotConnected != m_ssServerState); AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!"); // Put us in Authentication mode OnStatus(IXP_AUTHORIZING); // Check first if we're already authenticated (eg, by PREAUTH greeting) if (ssAuthenticated <= m_ssServerState) { // We were preauthed. Notify user that login is complete OnStatus(IXP_AUTHORIZED); return; } // Use the old "Login" trick (cleartext passwords and all) hrResult = TwoArgCommand("LOGIN", m_rServer.szUserName, m_rServer.szPassword, icLOGIN_COMMAND, ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER); if (FAILED(hrResult)) { char szFailureText[MAX_RESOURCESTRING]; // Could not send cmd: terminate login procedure and notify user LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText, ARRAYSIZE(szFailureText)); OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE); DropConnection(); } } // LoginUser //*************************************************************************** // Function: AuthenticateUser // // Purpose: // This function handles our behaviour during non-cleartext (SSPI) // authentication. It is very heavily based on CPOP3Transport::ResponseAUTH. // Note that due to server interpretation problems during testing, I decided // that BAD and NO responses will be treated as the same thing for purposes // of authentication. // // Arguments: // AUTH_EVENT aeEvent [in] - the authentication event currently occuring. // This can be something like aeCONTINUE, for instance. // LPSTR pszServerData [in] - any data from the server associated with the // current authentication event. Set to NULL if no data is applicable. // DWORD dwSizeOfData [in] - the size of the buffer pointed to by // pszServerData. //*************************************************************************** void CImap4Agent::AuthenticateUser(AUTH_EVENT aeEvent, LPSTR pszServerData, DWORD dwSizeOfData) { HRESULT hrResult; UINT uiFailureTextID; BOOL fUseLastResponse; // Initialize variables hrResult = S_OK; uiFailureTextID = 0; fUseLastResponse = FALSE; // Suspend the watchdog for this entire function LeaveBusy(); // Handle the events for which the current state is unimportant if (aeBAD_OR_NO_RESPONSE == aeEvent && asUNINITIALIZED < m_asAuthStatus.asCurrentState) { BOOL fTryNextAuthPkg; // Figure out whether we should try the next auth pkg, or re-try current if (asWAITFOR_CHALLENGE == m_asAuthStatus.asCurrentState || asWAITFOR_AUTHENTICATION == m_asAuthStatus.asCurrentState) fTryNextAuthPkg = tamCURRENT_AUTH_METHOD; else fTryNextAuthPkg = tamNEXT_AUTH_METHOD; // Send the AUTHENTICATE command hrResult = TryAuthMethod(fTryNextAuthPkg, &uiFailureTextID); if (FAILED(hrResult)) // No more auth methods to try: disconnect and end session fUseLastResponse = TRUE; else { // OK, wait for server response m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE; if (tamCURRENT_AUTH_METHOD == fTryNextAuthPkg) m_asAuthStatus.fPromptForCredentials = TRUE; } goto exit; } else if (aeABORT_AUTHENTICATION == aeEvent) { // We received an unknown tagged response from the server: bail hrResult = E_FAIL; uiFailureTextID = idsIMAPAbortAuth; fUseLastResponse = TRUE; goto exit; } // Now, process auth events based on our current state switch (m_asAuthStatus.asCurrentState) { case asUNINITIALIZED: { BOOL fResult; // Check conditions if (aeStartAuthentication != aeEvent) { AssertSz(FALSE, "You can only start authentication in this state"); break; } Assert(NULL == pszServerData && 0 == dwSizeOfData); // Put us in Authentication mode OnStatus(IXP_AUTHORIZING); // Check first if we're already authenticated (eg, by PREAUTH greeting) if (ssAuthenticated <= m_ssServerState) { // We were preauthed. Notify user that login is complete OnStatus(IXP_AUTHORIZED); break; } // Initialize SSPI fResult = FIsSicilyInstalled(); if (FALSE == fResult) { hrResult = E_FAIL; uiFailureTextID = idsIMAPSicilyInitFail; break; } hrResult = SSPIGetPackages(&m_asAuthStatus.pPackages, &m_asAuthStatus.cPackages); if (FAILED(hrResult)) { uiFailureTextID = idsIMAPSicilyPkgFailure; break; } // Send AUTHENTICATE command Assert(0 == m_asAuthStatus.iCurrentAuthToken); hrResult = TryAuthMethod(tamNEXT_AUTH_METHOD, &uiFailureTextID); if (FAILED(hrResult)) break; m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE; } // case asUNINITIALIZED break; // case asUNINITIALIZED case asWAITFOR_CONTINUE: { SSPIBUFFER Negotiate; if (aeCONTINUE != aeEvent) { AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?"); break; } // Server wants us to continue: send negotiation string hrResult = SSPILogon(&m_asAuthStatus.rSicInfo, m_asAuthStatus.fPromptForCredentials, SSPI_BASE64, m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1], &m_rServer, m_pCBHandler); if (FAILED(hrResult)) { // Suppress error reportage - user may have hit cancel hrResult = CancelAuthentication(); break; } if (m_asAuthStatus.fPromptForCredentials) { m_asAuthStatus.fPromptForCredentials = FALSE; // Don't prompt again } hrResult = SSPIGetNegotiate(&m_asAuthStatus.rSicInfo, &Negotiate); if (FAILED(hrResult)) { // Suppress error reportage - user may have hit cancel // Or the command was killed (with the connection) // Only cancel if we still have a pending command... if(m_piciCmdInSending) hrResult = CancelAuthentication(); break; } // Append CRLF to negotiation string Negotiate.szBuffer[Negotiate.cbBuffer - 1] = '\r'; Negotiate.szBuffer[Negotiate.cbBuffer] = '\n'; Negotiate.szBuffer[Negotiate.cbBuffer + 1] = '\0'; Negotiate.cbBuffer += 2; Assert(Negotiate.cbBuffer <= sizeof(Negotiate.szBuffer)); Assert(Negotiate.szBuffer[Negotiate.cbBuffer - 1] == '\0'); hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE, Negotiate.szBuffer, Negotiate.cbBuffer - 1); if (FAILED(hrResult)) break; m_asAuthStatus.asCurrentState = asWAITFOR_CHALLENGE; } // case asWAITFOR_CONTINUE break; // case asWAITFOR_CONTINUE case asWAITFOR_CHALLENGE: { SSPIBUFFER rChallenge, rResponse; int iChallengeLen; if (aeCONTINUE != aeEvent) { AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?"); break; } // Server has given us a challenge: respond to challenge SSPISetBuffer(pszServerData, SSPI_STRING, 0, &rChallenge); hrResult = SSPIResponseFromChallenge(&m_asAuthStatus.rSicInfo, &rChallenge, &rResponse); if (FAILED(hrResult)) { // Suppress error reportage - user could have hit cancel hrResult = CancelAuthentication(); break; } // Append CRLF to response string rResponse.szBuffer[rResponse.cbBuffer - 1] = '\r'; rResponse.szBuffer[rResponse.cbBuffer] = '\n'; rResponse.szBuffer[rResponse.cbBuffer + 1] = '\0'; rResponse.cbBuffer += 2; Assert(rResponse.cbBuffer <= sizeof(rResponse.szBuffer)); Assert(rResponse.szBuffer[rResponse.cbBuffer - 1] == '\0'); hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE, rResponse.szBuffer, rResponse.cbBuffer - 1); if (FAILED(hrResult)) break; if (FALSE == rResponse.fContinue) m_asAuthStatus.asCurrentState = asWAITFOR_AUTHENTICATION; } // case asWAITFOR_CHALLENGE break; // case asWAITFOR_CHALLENGE case asWAITFOR_AUTHENTICATION: // If OK response, do nothing if (aeOK_RESPONSE != aeEvent) { AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?"); break; } break; // case asWAITFOR_AUTHENTICATION case asCANCEL_AUTHENTICATION: AssertSz(aeBAD_OR_NO_RESPONSE == aeEvent, "I cancelled an authentication and didn't get BAD"); break; // case asCANCEL_AUTHENTICATION default: AssertSz(FALSE, "Invalid or unhandled state?"); break; // Default case } // switch (aeEvent) exit: if (FAILED(hrResult)) { char szFailureText[MAX_RESOURCESTRING]; char szFailureFmt[MAX_RESOURCESTRING/4]; char szGeneral[MAX_RESOURCESTRING/4]; // Ack, how big could the word, "General" be? LPSTR p, pszAuthPkg; LoadString(g_hLocRes, idsIMAPAuthFailedFmt, szFailureFmt, ARRAYSIZE(szFailureFmt)); if (0 == m_asAuthStatus.iCurrentAuthToken) { LoadString(g_hLocRes, idsGeneral, szGeneral, ARRAYSIZE(szGeneral)); pszAuthPkg = szGeneral; } else pszAuthPkg = m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1]; p = szFailureText; p += wnsprintf(szFailureText, ARRAYSIZE(szFailureText), szFailureFmt, pszAuthPkg); if (0 != uiFailureTextID) LoadString(g_hLocRes, uiFailureTextID, p, ARRAYSIZE(szFailureText) - (DWORD) (p - szFailureText)); OnIMAPError(hrResult, szFailureText, fUseLastResponse); DropConnection(); } // Reawaken the watchdog, if required else if (FALSE == m_fBusy && (NULL != m_piciPendingList || NULL != m_piciCmdInSending)) { hrResult = HrEnterBusy(); Assert(SUCCEEDED(hrResult)); } } // AuthenticateUser //*************************************************************************** // Function: TryAuthMethod // // Purpose: // This function sends out an AUTHENTICATE command to the server with the // appropriate authentication method. The caller can choose which method is // more appropriate: he can re-try the current authentication method, or // move on to the next authentication command which is supported by both // server and client. // // Arguments: // BOOL fNextAuthMethod [in] - TRUE if we should attempt to move on to // the next authentication package. FALSE if we should re-try the // current authentication package. // UINT *puiFailureTextID [out] - in case of failure, (eg, no more auth // methods to try), this function returns a string resource ID here which // describes the error. // // Returns: // HRESULT indicating success or failure. Expected failure codes include: // IXP_E_IMAP_AUTH_NOT_POSSIBLE - indicates server does not support // any auth packages which are recognized on this computer. // IXP_E_IMAP_OUT_OF_AUTH_METHODS - indicates that one or more auth // methods were attempted, and no more auth methods are left to try. //*************************************************************************** HRESULT CImap4Agent::TryAuthMethod(BOOL fNextAuthMethod, UINT *puiFailureTextID) { BOOL fFoundMatch; HRESULT hrResult; char szBuffer[CMDLINE_BUFSIZE]; CIMAPCmdInfo *piciCommand; int iStartingAuthToken; LPSTR p; Assert(m_lRefCount > 0); // Initialize variables hrResult = S_OK; piciCommand = NULL; // Only accept cmds if server is in proper state if (ssNonAuthenticated != m_ssServerState) { AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command."); return IXP_E_IMAP_IMPROPER_SVRSTATE; } // If we've already tried an auth pkg, free its info if (0 != m_asAuthStatus.iCurrentAuthToken) SSPIFreeContext(&m_asAuthStatus.rSicInfo); // Find the next auth token (returned by svr) that we support on this computer fFoundMatch = FALSE; iStartingAuthToken = m_asAuthStatus.iCurrentAuthToken; while (fFoundMatch == FALSE && m_asAuthStatus.iCurrentAuthToken < m_asAuthStatus.iNumAuthTokens && fNextAuthMethod) { ULONG ul = 0; // Current m_asAuthStatus.iCurrentAuthToken serves as idx to NEXT auth token // Compare current auth token with all installed packages for (ul = 0; ul < m_asAuthStatus.cPackages; ul++) { if (0 == lstrcmpi(m_asAuthStatus.pPackages[ul].pszName, m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken])) { fFoundMatch = TRUE; break; } // if } // for // Update this to indicate the current auth token ORDINAL (not idx) m_asAuthStatus.iCurrentAuthToken += 1; } // while if (FALSE == fFoundMatch && fNextAuthMethod) { // Could not find next authentication method match-up if (0 == iStartingAuthToken) { *puiFailureTextID = idsIMAPAuthNotPossible; return IXP_E_IMAP_AUTH_NOT_POSSIBLE; } else { *puiFailureTextID = idsIMAPOutOfAuthMethods; return IXP_E_IMAP_OUT_OF_AUTH_METHODS; } } // OK, m_asAuthStatus.iCurrentAuthToken should now point to correct match piciCommand = new CIMAPCmdInfo(this, icAUTHENTICATE_COMMAND, ssNonAuthenticated, 0, 0, NULL); if (NULL == piciCommand) { *puiFailureTextID = idsMemory; return E_OUTOFMEMORY; } // Construct command line p = szBuffer; p += wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s %.300s\r\n", piciCommand->szTag, "AUTHENTICATE", m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1]); // Send command hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, (DWORD) (p - szBuffer)); if (FAILED(hrResult)) goto SendError; // Insert a pause, so we can perform challenge/response hrResult = SendPause(piciCommand); if (FAILED(hrResult)) goto SendError; // Transmit command and register with IMAP response parser hrResult = SubmitIMAPCommand(piciCommand); SendError: if (FAILED(hrResult)) delete piciCommand; return hrResult; } // TryAuthMethod //*************************************************************************** // Function: CancelAuthentication // // Purpose: // This function cancels the authentication currently in progress, // typically due to a failure result from a Sicily function. It sends a "*" // to the server and puts us into cancellation mode. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::CancelAuthentication(void) { HRESULT hrResult; hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE, "*\r\n", 3); m_asAuthStatus.asCurrentState = asCANCEL_AUTHENTICATION; return hrResult; } // CancelAuthentication //*************************************************************************** // Function: OnCommandCompletion // // Purpose: // This function is called whenever we have received a tagged response line // terminating the current command in progress, whether or not the command // result was successful or not. This function notifies the user of the // command's results, and handles other tasks such as updating our internal // mirror of the server state and calling notification functions. // // Arguments: // LPSTR szTag [in] - the tag found in the tagged response line. This will // be used to compare with a list of commands in progress when we allow // multiple simultaneous commands, but is not currently used. // HRESULT hrCompletionResult [in] - the HRESULT returned by the IMAP line // parsing functions, eg, S_OK or IXP_E_IMAP_SVR_SYNTAXERR. // IMAP_RESPONSE_ID irCompletionResponse [in] - identifies the status response // of the tagged response line (OK/NO/BAD). //*************************************************************************** void CImap4Agent::OnCommandCompletion(LPSTR szTag, HRESULT hrCompletionResult, IMAP_RESPONSE_ID irCompletionResponse) { CIMAPCmdInfo *piciCompletedCmd; boolean bSuppressCompletionNotification; Assert(m_lRefCount > 0); Assert(NULL != szTag); Assert(NULL != m_piciPendingList || NULL != m_piciCmdInSending); bSuppressCompletionNotification = FALSE; // ** STEP ONE: Identify the corresponding command for given tagged response // Search the pending-command chain for the given tag piciCompletedCmd = RemovePendingCommand(szTag); if (NULL == piciCompletedCmd) { BOOL fLeaveBusy = FALSE; // Couldn't find in pending list, check the command in sending EnterCriticalSection(&m_csSendQueue); if (NULL != m_piciCmdInSending && 0 == lstrcmp(szTag, m_piciCmdInSending->szTag)) { piciCompletedCmd = DequeueCommand(); fLeaveBusy = TRUE; } else { AssertSz(FALSE, "Could not find cmd corresponding to tagged response!"); } LeaveCriticalSection(&m_csSendQueue); // Now we're out of &m_csSendQueue, call LeaveBusy (needs m_cs). Avoids deadlock. if (fLeaveBusy) LeaveBusy(); // This needs CIxpBase::m_cs, so having &m_csSendQueue may deadlock } // Did we find a command which matches the given tag? if (NULL == piciCompletedCmd) return; // $REVIEW: Should probably return an error to user // $REVIEW: I don't think I need to bother to pump the send queue // ** STEP TWO: Perform end-of-command actions // Translate hrCompletionResult depending on response received switch (irCompletionResponse) { case irOK_RESPONSE: Assert(S_OK == hrCompletionResult); break; case irNO_RESPONSE: Assert(S_OK == hrCompletionResult); hrCompletionResult = IXP_E_IMAP_TAGGED_NO_RESPONSE; break; case irBAD_RESPONSE: Assert(S_OK == hrCompletionResult); hrCompletionResult = IXP_E_IMAP_BAD_RESPONSE; break; default: // If none of the above, hrResult had better be failure Assert(FAILED(hrCompletionResult)); break; } // Perform any actions which follow the successful (or unsuccessful) // completion of an IMAP command switch (piciCompletedCmd->icCommandID) { case icAUTHENTICATE_COMMAND: { AUTH_EVENT aeEvent; // We always suppress completion notification for this command, // because it is sent by internal code (not by the user) bSuppressCompletionNotification = TRUE; if (irOK_RESPONSE == irCompletionResponse) aeEvent = aeOK_RESPONSE; else if (irNO_RESPONSE == irCompletionResponse || irBAD_RESPONSE == irCompletionResponse) aeEvent = aeBAD_OR_NO_RESPONSE; else aeEvent = aeABORT_AUTHENTICATION; AuthenticateUser(aeEvent, NULL, 0); if (SUCCEEDED(hrCompletionResult)) { m_ssServerState = ssAuthenticated; AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!"); OnStatus(IXP_AUTHORIZED); } // Make sure we were paused Assert(iltPAUSE == piciCompletedCmd->pilqCmdLineQueue-> pilfFirstFragment->iltFragmentType); } // case icAUTHENTICATE_COMMAND break; // case icAUTHENTICATE_COMMAND case icLOGIN_COMMAND: // We always suppress completion notification for this command, // because it is sent by internal code (not by the user) bSuppressCompletionNotification = TRUE; if (SUCCEEDED(hrCompletionResult)) { m_ssServerState = ssAuthenticated; AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!"); OnStatus(IXP_AUTHORIZED); } else { char szFailureText[MAX_RESOURCESTRING]; Assert(ssAuthenticated > m_ssServerState); LoadString(g_hLocRes, idsFailedLogin, szFailureText, ARRAYSIZE(szFailureText)); OnIMAPError(IXP_E_IMAP_LOGINFAILURE, szFailureText, USE_LAST_RESPONSE); ReLoginUser(); // Re-attempt login } // else break; // case icLOGIN_COMMAND case icCAPABILITY_COMMAND: // We always suppress completion notification for this command // because it is sent by internal code (not by the user) bSuppressCompletionNotification = TRUE; if (SUCCEEDED(hrCompletionResult)) { AssertSz(m_fConnectAuth, "Now just HOW does IIMAPTransport user do auth?"); if (m_rServer.fTrySicily) AuthenticateUser(aeStartAuthentication, NULL, 0); else LoginUser(); } else { char szFailureText[MAX_RESOURCESTRING]; // Stop login process and report error to caller LoadString(g_hLocRes, idsIMAPFailedCapability, szFailureText, ARRAYSIZE(szFailureText)); OnIMAPError(hrCompletionResult, szFailureText, USE_LAST_RESPONSE); DropConnection(); } break; // case icCAPABILITY_COMMAND case icSELECT_COMMAND: case icEXAMINE_COMMAND: if (SUCCEEDED(hrCompletionResult)) m_ssServerState = ssSelected; else m_ssServerState = ssAuthenticated; break; // case icSELECT_COMMAND and icEXAMINE_COMMAND case icCLOSE_COMMAND: // $REVIEW: Should tagged NO response also go to ssAuthenticated? if (SUCCEEDED(hrCompletionResult)) { m_ssServerState = ssAuthenticated; ResetMsgSeqNumToUID(); } break; // case icCLOSE_COMMAND case icLOGOUT_COMMAND: // We always suppress completion notification for this command bSuppressCompletionNotification = TRUE; // User can't send logout: it's sent internally // Drop the connection (without status indication) regardless of // whether LOGOUT succeeded or failed Assert(SUCCEEDED(hrCompletionResult)); // Debug-only detection of hanky-panky m_pSocket->Close(); ResetMsgSeqNumToUID(); // Just in case, SHOULD be handled by OnDisconnected,FreeAllData break; // case icLOGOUT_COMMAND; case icIDLE_COMMAND: bSuppressCompletionNotification = TRUE; // User can't send IDLE: it's sent internally m_fIDLE = FALSE; // We are now out of IDLE mode break; // case icIDLE_COMMAND case icAPPEND_COMMAND: m_dwAppendStreamUploaded = 0; m_dwAppendStreamTotal = 0; break; // case icAPPEND_COMMAND } // switch (piciCompletedCmd->icCommandID) // ** STEP THREE: Perform notifications. // Notify the user that this command has completed, unless we're told to // suppress it (usually done to treat the multi-step login process as // one operation). if (FALSE == bSuppressCompletionNotification) { IMAP_RESPONSE irIMAPResponse; irIMAPResponse.wParam = piciCompletedCmd->wParam; irIMAPResponse.lParam = piciCompletedCmd->lParam; irIMAPResponse.hrResult = hrCompletionResult; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION; OnIMAPResponse(piciCompletedCmd->pCBHandler, &irIMAPResponse); } // Delete CIMAPCmdInfo object // Note that deleting a CIMAPCmdInfo object automatically flushes its send queue delete piciCompletedCmd; // Finally, pump the send queue, if another cmd is available if (NULL != m_piciSendQueue) ProcessSendQueue(iseSEND_COMMAND); else if (NULL == m_piciPendingList && m_ssServerState >= ssAuthenticated && irsIDLE == m_irsState) // Both m_piciSendQueue and m_piciPendingList are empty: send IDLE cmd EnterIdleMode(); } // OnCommandCompletion //*************************************************************************** // Function: CheckForCompleteResponse // // Purpose: // Given a response line (which isn't part of a literal), this function // checks the end of the line to see if a literal is coming. If so, then we // prepare the receiver FSM for it. Otherwise, this constitutes the end // of an IMAP response, so we may parse as required. // // Arguments: // LPSTR pszResponseLine [in] - this points to the response line sent to // us by the IMAP server. // DWORD dwNumBytesRead [in] - the length of pszResponseLine. // IMAP_RESPONSE_ID *pirParseResult [out] - if the function determines that // we can parse the response, the parse result is stored here (eg, // irOK_RESPONSE). Otherwise, irNONE is written to the pointed location. //*************************************************************************** void CImap4Agent::CheckForCompleteResponse(LPSTR pszResponseLine, DWORD dwNumBytesRead, IMAP_RESPONSE_ID *pirParseResult) { HRESULT hrResult; boolean bTagged; IMAP_LINE_FRAGMENT *pilfLine; LPSTR psz; BOOL fLiteral = FALSE; Assert(m_lRefCount > 0); Assert(NULL != pszResponseLine); Assert(NULL == m_pilfLiteralInProgress); Assert(0 == m_dwLiteralInProgressBytesLeft); Assert(NULL != pirParseResult); Assert(irsIDLE == m_irsState || irsSVR_GREETING == m_irsState); *pirParseResult = irNONE; // This is a LINE (not literal), so we're OK to nuke CRLF at end Assert(dwNumBytesRead >= 2); // All lines must have at least CRLF *(pszResponseLine + dwNumBytesRead - 2) = '\0'; // Create line fragment pilfLine = new IMAP_LINE_FRAGMENT; pilfLine->iltFragmentType = iltLINE; pilfLine->ilsLiteralStoreType = ilsSTRING; pilfLine->dwLengthOfFragment = dwNumBytesRead - 2; // Subtract nuked CRLF pilfLine->data.pszSource = pszResponseLine; pilfLine->pilfNextFragment = NULL; pilfLine->pilfPrevFragment = NULL; EnqueueFragment(pilfLine, &m_ilqRecvQueue); // Now check last char in line (exclude CRLF) to see if a literal is forthcoming psz = pszResponseLine + dwNumBytesRead - min(dwNumBytesRead, 3); // Points to '}' if literal is coming if ('}' == *psz) { LPSTR pszLiteral; // IE5 bug #30672: It is valid for a line to end in "}" and not be a literal. // We must confirm that there are digits and an opening brace "{" to detect a literal pszLiteral = psz; while (TRUE) { pszLiteral -= 1; if (pszLiteral < pszResponseLine) break; if ('{' == *pszLiteral) { fLiteral = TRUE; psz = pszLiteral; break; } else if (*pszLiteral < '0' || *pszLiteral > '9') // Assert(FALSE) (placeholder) // *** Consider using isdigit or IsDigit? *** break; // This is not a literal } } if (FALSE == fLiteral) { char szTag[NUM_TAG_CHARS+1]; // No literal is forthcoming. This is a complete line, so let's parse // Get ptr to first fragment, then nuke receive queue so we can // continue to receive response lines while parsing this one pilfLine = m_ilqRecvQueue.pilfFirstFragment; m_ilqRecvQueue = ImapLinefragQueue_INIT; // Parse line. Note that parsing code is responsible for advancing // pilfLine so that it points to the current fragment being parsed. // Fragments which have been fully processed should be freed by // the parsing code (except for the last fragment) hrResult = ParseSvrResponseLine(&pilfLine, &bTagged, szTag, pirParseResult); // Flush rest of recv queue, regardless of parse result while (NULL != pilfLine) { IMAP_LINE_FRAGMENT *pilfTemp; pilfTemp = pilfLine->pilfNextFragment; FreeFragment(&pilfLine); pilfLine = pilfTemp; } if (bTagged) OnCommandCompletion(szTag, hrResult, *pirParseResult); else if (FAILED(hrResult)) { IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Report untagged response failures via ErrorNotification callback GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, *pirParseResult); irIMAPResponse.hrResult = hrResult; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtERROR_NOTIFICATION; // Log it if (m_pLogFile) { char szErrorTxt[64]; wnsprintf(szErrorTxt, ARRAYSIZE(szErrorTxt), "PARSE ERROR: hr=%lu", hrResult); m_pLogFile->WriteLog(LOGFILE_DB, szErrorTxt); } OnIMAPResponse(pCBHandler, &irIMAPResponse); } } else { DWORD dwLengthOfLiteral, dwMsgSeqNum; LPSTR pszBodyTag; if ('{' != *psz) { Assert(FALSE); // What is this? return; // Nothing we can do, we obviously can't get size of literal } else dwLengthOfLiteral = StrToUint(psz + 1); // Prepare either for FETCH body, or a regular literal if (isFetchResponse(&m_ilqRecvQueue, &dwMsgSeqNum) && isFetchBodyLiteral(pilfLine, psz, &pszBodyTag)) { // Prepare (tombstone) literal first, because it puts us in literal mode hrResult = PrepareForLiteral(0); // This will override literal mode, putting us in fetch body part mode // Ignore PrepareForLiteral failure: if we don't, we interpret EACH LINE of // the fetch body as an IMAP response line. PrepareForFetchBody(dwMsgSeqNum, dwLengthOfLiteral, pszBodyTag); } else hrResult = PrepareForLiteral(dwLengthOfLiteral); Assert(SUCCEEDED(hrResult)); // Not much else we can do } // else: handles case where a literal is coming after this line } // CheckForCompleteResponse //*************************************************************************** // Function: PrepareForLiteral // // Purpose: // This function prepares the receiver code to receive a literal from the // IMAP server. // // Arguments: // DWORD dwSizeOfLiteral [in] - the size of the incoming literal as // reported by the IMAP server. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::PrepareForLiteral(DWORD dwSizeOfLiteral) { IMAP_LINE_FRAGMENT *pilfLiteral; HRESULT hrResult; // Initialize variables hrResult = S_OK; // Construct line fragment of type iltLITERAL Assert(NULL == m_pilfLiteralInProgress); pilfLiteral = new IMAP_LINE_FRAGMENT; if (NULL == pilfLiteral) return E_OUTOFMEMORY; pilfLiteral->iltFragmentType = iltLITERAL; pilfLiteral->dwLengthOfFragment = dwSizeOfLiteral; pilfLiteral->pilfNextFragment = NULL; pilfLiteral->pilfPrevFragment = NULL; // Allocate string or stream to hold literal, depending on its size if (pilfLiteral->dwLengthOfFragment > dwLITERAL_THRESHOLD) { // Literal is big, so store it as stream (large literals often represent // data which we return to the user as a stream, eg, message bodies) pilfLiteral->ilsLiteralStoreType = ilsSTREAM; hrResult = MimeOleCreateVirtualStream(&pilfLiteral->data.pstmSource); } else { BOOL bResult; // Literal is small. Store it as a string rather than a stream, since // CImap4Agent functions probably expect it as a string, anyways. pilfLiteral->ilsLiteralStoreType = ilsSTRING; bResult = MemAlloc((void **) &pilfLiteral->data.pszSource, pilfLiteral->dwLengthOfFragment + 1); // Room for null-term if (FALSE == bResult) hrResult = E_OUTOFMEMORY; else { hrResult = S_OK; *(pilfLiteral->data.pszSource) = '\0'; // Null-terminate the string } } if (FAILED(hrResult)) delete pilfLiteral; // Failure means no data.pstmSource or data.pszSource to dealloc else { // Set up receive FSM to receive the proper number of bytes for literal m_pilfLiteralInProgress = pilfLiteral; m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral; m_irsState = irsLITERAL; } return hrResult; } // PrepareForLiteral //*************************************************************************** // Function: isFetchResponse // // Purpose: // This function determines if the given IMAP line fragment queue holds // a FETCH response. If so, its message sequence number may be returned to // the caller. // // Arguments: // IMAP_LINEFRAG_QUEUE *pilqCurrentResponse [in] - a line fragment queue // which may or may not hold a FETCH response. // LPDWORD pdwMsgSeqNum [out] - if pilqCurrentResponse points to a FETCH // response, its message sequence number is returned here. This argument // may be NULL if the user does not care. // // Returns: // TRUE if pilqCurrentResponse held a FETCH response. Otherwise, FALSE. //*************************************************************************** BOOL CImap4Agent::isFetchResponse(IMAP_LINEFRAG_QUEUE *pilqCurrentResponse, LPDWORD pdwMsgSeqNum) { LPSTR pszMsgSeqNum; Assert(NULL != pilqCurrentResponse); Assert(NULL != pilqCurrentResponse->pilfFirstFragment); Assert(iltLINE == pilqCurrentResponse->pilfFirstFragment->iltFragmentType); if (NULL != pdwMsgSeqNum) *pdwMsgSeqNum = 0; // At least it won't be random pszMsgSeqNum = pilqCurrentResponse->pilfFirstFragment->data.pszSource; // Advance pointer to the message sequence number if ('*' != *pszMsgSeqNum) return FALSE; // We only handle tagged responses pszMsgSeqNum += 1; if (cSPACE != *pszMsgSeqNum) return FALSE; pszMsgSeqNum += 1; if (*pszMsgSeqNum >= '0' && *pszMsgSeqNum <= '9') { LPSTR pszEndOfNumber; int iResult; pszEndOfNumber = StrChr(pszMsgSeqNum, cSPACE); // Find the end of the number if (NULL == pszEndOfNumber) return FALSE; // This ain't no FETCH response iResult = StrCmpNI(pszEndOfNumber + 1, "FETCH ", 6); if (0 == iResult) { if (NULL != pdwMsgSeqNum) *pdwMsgSeqNum = StrToUint(pszMsgSeqNum); return TRUE; } } // If we hit this point, it wasn't a FETCH response return FALSE; } // isFetchResponse //*************************************************************************** // Function: isFetchBodyLiteral // // Purpose: // This function is called when the caller knows he has a FETCH response, // and when the FETCH response is about to send a literal. This function will // determine whether the literal about to be sent contains a message body // part (like RFC822), or whether the literal is something else (like an // nstring sent as a literal inside a BODYSTRUCTURE). // // Arguments: // IMAP_LINE_FRAGMENT *pilfCurrent [in] - a pointer to the current line // fragment received from the server. It is used by this function to // rewind past any literals we may have received in the "section" of // the BODY "msg_att" (see RFC2060 formal syntax). // LPSTR pszStartOfLiteralSize [in] - a pointer to the start of the '{' // character which indicates that a literal is coming (eg, {123} // indicates a literal of size 123 is coming, and pszStartOfLiteralSize // would point to the '{' in this case). // LPSTR *ppszBodyTag [out] - if the literal about to be sent contains a // message body part, a dup of the tag (eg, "RFC822" or "BODY[2.2]") is // returned to the caller here. It is the caller's responsibility to // MemFree this tag. THIS TAG WILL NOT CONTAIN ANY SPACES. Thus even though // the server may return "BODY[HEADER.FIELDS (foo bar)]", this function // only returns "BODY[HEADER.FIELDS". // // Returns: // TRUE if the literal about to be sent contains a message body part. // FALSE otherwise. //*************************************************************************** BOOL CImap4Agent::isFetchBodyLiteral(IMAP_LINE_FRAGMENT *pilfCurrent, LPSTR pszStartOfLiteralSize, LPSTR *ppszBodyTag) { LPSTR pszStartOfLine; LPSTR pszStartOfFetchAtt; LPSTR pszMostRecentSpace; int iNumDelimiters; BOOL fBodySection = FALSE; Assert(NULL != pilfCurrent); Assert(NULL != pszStartOfLiteralSize); Assert(pszStartOfLiteralSize >= pilfCurrent->data.pszSource && pszStartOfLiteralSize < (pilfCurrent->data.pszSource + pilfCurrent->dwLengthOfFragment)); Assert(NULL != ppszBodyTag); // Initialize variables *ppszBodyTag = NULL; Assert('{' == *pszStartOfLiteralSize); // Get pointer to current msg_att: we only care about RFC822* or BODY[...]. ENVELOPE ({5} doesn't count iNumDelimiters = 0; pszStartOfLine = pilfCurrent->data.pszSource; pszStartOfFetchAtt = pszStartOfLiteralSize; pszMostRecentSpace = pszStartOfLiteralSize; while (iNumDelimiters < 2) { // Check if we have recoiled to the start of current string buffer if (pszStartOfFetchAtt <= pszStartOfLine) { // We need to recoil to previous string buffer. It is likely that a literal // is in the way, and it is likely that this literal belongs to HEADER.FIELDS // (but this can also happen inside an ENVELOPE) // Skip literals and anything else that's not a line do { pilfCurrent = pilfCurrent->pilfPrevFragment; } while (NULL != pilfCurrent && iltLINE != pilfCurrent->iltFragmentType); if (NULL == pilfCurrent || 0 == pilfCurrent->dwLengthOfFragment) { // This ain't no FETCH BODY, near as I can tell Assert(iNumDelimiters < 2); break; } else { // Reset string pointers Assert(iltLINE == pilfCurrent->iltFragmentType && ilsSTRING == pilfCurrent->ilsLiteralStoreType); pszStartOfLine = pilfCurrent->data.pszSource; // Note that pszStartOfFetchAtt will recoil past literal size decl ("{123}") // That's OK because it won't contain any of the delimiters we're looking for pszStartOfFetchAtt = pszStartOfLine + pilfCurrent->dwLengthOfFragment; // Points to null-term pszMostRecentSpace = pszStartOfFetchAtt; // Points to null-term (that's OK) } } // Set pszMostRecentSpace before pszStartOfFetchAtt decrement so pszMostRecentSpace // isn't set to the space BEFORE the fetch body tag if (cSPACE == *pszStartOfFetchAtt) pszMostRecentSpace = pszStartOfFetchAtt; pszStartOfFetchAtt -= 1; // Check for nested brackets (should not be allowed) Assert(']' != *pszStartOfFetchAtt || fBodySection == FALSE); // Disable delimiter-counting if we're in the middle of RFC2060 formal syntax "section" // because the HEADER.FIELDS (...) section contains spaces and parentheses if (']' == *pszStartOfFetchAtt) fBodySection = TRUE; else if ('[' == *pszStartOfFetchAtt) fBodySection = FALSE; if (FALSE == fBodySection && (cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt)) iNumDelimiters += 1; } if (iNumDelimiters < 2) return FALSE; // This isn't a body tag Assert(2 == iNumDelimiters); Assert(cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt); pszStartOfFetchAtt += 1; // Make it point to the start of the tag if (0 == StrCmpNI(pszStartOfFetchAtt, "RFC822", 6) || 0 == StrCmpNI(pszStartOfFetchAtt, "BODY[", 5)) { int iSizeOfBodyTag; BOOL fResult; Assert(pszMostRecentSpace >= pszStartOfLine && (NULL == pilfCurrent || pszMostRecentSpace <= pszStartOfLine + pilfCurrent->dwLengthOfFragment)); Assert(pszStartOfFetchAtt >= pszStartOfLine && (NULL == pilfCurrent || pszStartOfFetchAtt <= pszStartOfLine + pilfCurrent->dwLengthOfFragment)); Assert(pszMostRecentSpace >= pszStartOfFetchAtt); // Return a duplicate of the body tag, up until the first space +1 for null-term iSizeOfBodyTag = (int) (pszMostRecentSpace - pszStartOfFetchAtt + 1); fResult = MemAlloc((void **)ppszBodyTag, iSizeOfBodyTag); if (FALSE == fResult) return FALSE; CopyMemory(*ppszBodyTag, pszStartOfFetchAtt, iSizeOfBodyTag); *(*ppszBodyTag + iSizeOfBodyTag - 1) = '\0'; // Null-terminate the body tag dup return TRUE; } // If we reached this point, this is not a body tag return FALSE; } // isFetchBodyLiteral //*************************************************************************** // Function: PrepareForFetchBody // // Purpose: // This function prepares the receiver code to receive a literal which // contains a message body part. This literal will always be part of a FETCH // response from the IMAP server. // // Arguments: // DWORD dwMsgSeqNum [in] - the message sequence number of the FETCH // response currently being received from the IMAP server. // DWORD dwSizeOfLiteral [in] - the size of the literal about to be received // from the server. // LPSTR pszBodyTag [in] - a pointer to a dup of the IMAP msg_att (eg, // "RFC822" or "BODY[2.2]") which identifies the current literal. Look up // msg_att in RFC2060's formal syntax section for details. This dup will // be MemFree'ed when it is no longer needed. //*************************************************************************** void CImap4Agent::PrepareForFetchBody(DWORD dwMsgSeqNum, DWORD dwSizeOfLiteral, LPSTR pszBodyTag) { Assert(0 == m_dwLiteralInProgressBytesLeft); m_fbpFetchBodyPartInProgress.dwMsgSeqNum = dwMsgSeqNum; m_fbpFetchBodyPartInProgress.pszBodyTag = pszBodyTag; m_fbpFetchBodyPartInProgress.dwTotalBytes = dwSizeOfLiteral; m_fbpFetchBodyPartInProgress.dwSizeOfData = 0; m_fbpFetchBodyPartInProgress.dwOffset = 0; m_fbpFetchBodyPartInProgress.fDone = 0; m_fbpFetchBodyPartInProgress.pszData = NULL; // Leave the cookies alone, so they persist throughout FETCH response m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral; m_irsState = irsFETCH_BODY; } // PrepareForFetchBody //*************************************************************************** // Function: AddBytesToLiteral // // Purpose: // This function is called whenever we receive an AE_RECV from the IMAP // server while the receiver FSM is in irsLITERAL mode. The caller is // expected to call CASyncConn::ReadBytes and update the literal byte-count. // This function just handles the buffer-work. // // Arguments: // LPSTR pszResponseBuf [in] - the buffer of data returned via // CASyncConn::ReadBytes. // DWORD dwNumBytesRead [in] - the size of the buffer pointed to by // CASyncConn::ReadBytes. //*************************************************************************** void CImap4Agent::AddBytesToLiteral(LPSTR pszResponseBuf, DWORD dwNumBytesRead) { Assert(m_lRefCount > 0); Assert(NULL != pszResponseBuf); if (NULL == m_pilfLiteralInProgress) { AssertSz(FALSE, "I'm still in irsLITERAL state, but I'm not set up to recv literals!"); m_irsState = irsIDLE; goto exit; } // Find out if this literal will be stored as a string or stream (this // decision was made in CheckForCompleteResponse using size of literal). Assert(iltLITERAL == m_pilfLiteralInProgress->iltFragmentType); if (ilsSTREAM == m_pilfLiteralInProgress->ilsLiteralStoreType) { HRESULT hrResult; ULONG ulNumBytesWritten; // Store literal as stream hrResult = (m_pilfLiteralInProgress->data.pstmSource)->Write(pszResponseBuf, dwNumBytesRead, &ulNumBytesWritten); Assert(SUCCEEDED(hrResult) && ulNumBytesWritten == dwNumBytesRead); } else { LPSTR pszLiteralStartPoint; // Concatenate literal to literal in progress // $REVIEW: Perf enhancement - CALCULATE insertion point pszLiteralStartPoint = m_pilfLiteralInProgress->data.pszSource + lstrlen(m_pilfLiteralInProgress->data.pszSource); Assert(pszLiteralStartPoint + dwNumBytesRead <= m_pilfLiteralInProgress->data.pszSource + m_pilfLiteralInProgress->dwLengthOfFragment); CopyMemory(pszLiteralStartPoint, pszResponseBuf, dwNumBytesRead); *(pszLiteralStartPoint + dwNumBytesRead) = '\0'; // Null-terminate } // Check for end-of-literal if (0 == m_dwLiteralInProgressBytesLeft) { // We now have the complete literal! Queue it up and move on EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue); m_irsState = irsIDLE; m_pilfLiteralInProgress = NULL; } exit: SafeMemFree(pszResponseBuf); } // AddBytesToLiteral //*************************************************************************** // Function: DispatchFetchBodyPart // // Purpose: // This function is called whenever receive a packet which is part of a // message body part of a FETCH response. This packet is dispatched to the // caller in this function via the OnResponse(irtFETCH_BODY) callback. If // the message body part is finished, this function also restores the // receiver code to receive lines so that the FETCH response may be completed. // // Arguments: // LPSTR pszResponseBuf [in] - a pointer to the packet which is part of // the message body part of the current FETCH response. // DWORD dwNumBytesRead [in] - the size of the data pointed to by // pszResponseBuf. // BOOL fFreeBodyTagAtEnd [in] - TRUE if // m_fbpFetchBodyPartInProgress.pszBodyTag points to a string dup, in // which case it must be MemFree'ed when the message body part is // finished. FALSE if the pszBodyTag member must not be MemFree'ed. //*************************************************************************** void CImap4Agent::DispatchFetchBodyPart(LPSTR pszResponseBuf, DWORD dwNumBytesRead, BOOL fFreeBodyTagAtEnd) { IMAP_RESPONSE irIMAPResponse; AssertSz(0 != m_fbpFetchBodyPartInProgress.dwMsgSeqNum, "Are you sure you're set up to receive a Fetch Body Part?"); // Update the FETCH body part structure m_fbpFetchBodyPartInProgress.dwSizeOfData = dwNumBytesRead; m_fbpFetchBodyPartInProgress.pszData = pszResponseBuf; m_fbpFetchBodyPartInProgress.fDone = (m_fbpFetchBodyPartInProgress.dwOffset + dwNumBytesRead >= m_fbpFetchBodyPartInProgress.dwTotalBytes); // Send an IMAP response callback for this body part irIMAPResponse.wParam = 0; irIMAPResponse.lParam = 0; irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = NULL; // Not relevant irIMAPResponse.irtResponseType = irtFETCH_BODY; irIMAPResponse.irdResponseData.pFetchBodyPart = &m_fbpFetchBodyPartInProgress; AssertSz(S_OK == irIMAPResponse.hrResult, "Make sure fDone is TRUE if FAILED(hrResult))"); OnIMAPResponse(m_pCBHandler, &irIMAPResponse); // Update the next buffer's offset m_fbpFetchBodyPartInProgress.dwOffset += dwNumBytesRead; // Check for end of body part if (m_fbpFetchBodyPartInProgress.dwOffset >= m_fbpFetchBodyPartInProgress.dwTotalBytes) { Assert(0 == m_dwLiteralInProgressBytesLeft); Assert(TRUE == m_fbpFetchBodyPartInProgress.fDone); Assert(m_fbpFetchBodyPartInProgress.dwOffset == m_fbpFetchBodyPartInProgress.dwTotalBytes); if (fFreeBodyTagAtEnd) MemFree(m_fbpFetchBodyPartInProgress.pszBodyTag); // Enqueue the tombstone literal, if fetch body nstring was sent as literal if (NULL != m_pilfLiteralInProgress) { EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue); m_pilfLiteralInProgress = NULL; } // Zero the fetch body part structure, but leave the cookies PrepareForFetchBody(0, 0, NULL); m_irsState = irsIDLE; // Overrides irsFETCH_BODY set by PrepareForFetchBody } else { Assert(FALSE == m_fbpFetchBodyPartInProgress.fDone); } } // DispatchFetchBodyPart //*************************************************************************** // Function: UploadStreamProgress // // Purpose: // This function sends irtAPPEND_PROGRESS responses to the callback so // that the IIMAPTransport user can report the progress of an APPEND command. // // Arguments: // DWORD dwBytesUploaded [in] - number of bytes just uploaded to the // server. This function retains a running count of bytes uploaded. //*************************************************************************** void CImap4Agent::UploadStreamProgress(DWORD dwBytesUploaded) { APPEND_PROGRESS ap; IMAP_RESPONSE irIMAPResponse; // Check if we should report APPEND upload progress. We report if we are currently executing // APPEND and the CRLF is waiting to be sent if (NULL == m_piciCmdInSending || icAPPEND_COMMAND != m_piciCmdInSending->icCommandID || NULL == m_piciCmdInSending->pilqCmdLineQueue) return; else { IMAP_LINE_FRAGMENT *pilf = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment; // It's an APPEND command with non-empty linefrag queue, now check that next // linefrag fits description for linefrag after msg body if (NULL == pilf || iltLINE != pilf->iltFragmentType || ilsSTRING != pilf->ilsLiteralStoreType || 2 != pilf->dwLengthOfFragment || '\r' != pilf->data.pszSource[0] || '\n' != pilf->data.pszSource[1] || NULL != pilf->pilfNextFragment) return; } // Report current progress of message upload m_dwAppendStreamUploaded += dwBytesUploaded; ap.dwUploaded = m_dwAppendStreamUploaded; ap.dwTotal = m_dwAppendStreamTotal; Assert(0 != ap.dwTotal); Assert(ap.dwTotal >= ap.dwUploaded); irIMAPResponse.wParam = m_piciCmdInSending->wParam; irIMAPResponse.lParam = m_piciCmdInSending->lParam; irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = NULL; irIMAPResponse.irtResponseType = irtAPPEND_PROGRESS; irIMAPResponse.irdResponseData.papAppendProgress = ≈ OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse); } // UploadStreamProgress //*************************************************************************** // Function: OnNotify // // Purpose: This function is required for the IAsyncConnCB which we derive // from (callback for CAsyncConn class). This function acts on CASyncConn // state changes and events. //*************************************************************************** void CImap4Agent::OnNotify(ASYNCSTATE asOld, ASYNCSTATE asNew, ASYNCEVENT ae) { char szLogFileLine[128]; // Check refcount, but exception is that we can get AE_CLOSE. CImap4Agent's // destructor calls CASyncConn's Close() member, which generates one last // message, the event AE_CLOSE, with m_lRefCount == 0. Assert(m_lRefCount > 0 || (0 == m_lRefCount && AE_CLOSE == ae)); // Record AsyncConn event/state-change in log file wnsprintf(szLogFileLine, ARRAYSIZE(szLogFileLine), "OnNotify: asOld = %d, asNew = %d, ae = %d", asOld, asNew, ae); if (m_pLogFile) m_pLogFile->WriteLog(LOGFILE_DB, szLogFileLine); // Check for disconnect if (AS_DISCONNECTED == asNew) { m_irsState = irsNOT_CONNECTED; m_ssServerState = ssNotConnected; m_fIDLE = FALSE; m_bFreeToSend = TRUE; } // Act on async event switch (ae) { case AE_RECV: { HRESULT hrResult; // Process response lines until no more lines (hrIncomplete result) do { hrResult = ProcessResponseLine(); } while (SUCCEEDED(hrResult)); // If error is other than IXP_E_INCOMPLETE, drop connection if (IXP_E_INCOMPLETE != hrResult) { char szFailureText[MAX_RESOURCESTRING]; // Looks fatal, better warn the user that disconnection is imminent LoadString(g_hLocRes, idsIMAPSocketReadError, szFailureText, ARRAYSIZE(szFailureText)); OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE); // What else can we do but drop the connection? DropConnection(); } // if error other than IXP_E_INCOMPLETE break; } // case AE_RECV case AE_SENDDONE: UploadStreamProgress(m_pSocket->UlGetSendByteCount()); // Received AE_SENDDONE from CAsyncConn class. We are free to send more data m_bFreeToSend = TRUE; ProcessSendQueue(iseSENDDONE); // Informs them that they may start sending again break; case AE_WRITE: UploadStreamProgress(m_pSocket->UlGetSendByteCount()); break; default: CIxpBase::OnNotify(asOld, asNew, ae); break; // case default } // switch (ae) } // OnNotify //*************************************************************************** // Function: ProcessResponseLine // // Purpose: // This functions handles the AE_RECV event of the OnNotify() callback. // It gets a response line from the server (if available) and dispatches // the line to the proper recipient based on the state of the receiver FSM. // // Returns: // HRESULT indicating success or failure of CAsyncConn line retrieval. // hrIncomplete (an error code) is returned if no more complete lines can // be retrieved from CAsyncConn's buffer. //*************************************************************************** HRESULT CImap4Agent::ProcessResponseLine(void) { HRESULT hrASyncResult; char *pszResponseBuf; int cbRead; Assert(m_lRefCount > 0); // We are always in one of two modes: line mode, or byte mode. Figure out which. if (irsLITERAL != m_irsState && irsFETCH_BODY != m_irsState) { // We're in line mode. Get response line from server hrASyncResult = m_pSocket->ReadLine(&pszResponseBuf, &cbRead); if (FAILED(hrASyncResult)) return hrASyncResult; // Record received line in log file if (m_pLogFile) m_pLogFile->WriteLog(LOGFILE_RX, pszResponseBuf); } // if-line mode else { // We're in literal mode. Get as many bytes as we can. hrASyncResult = m_pSocket->ReadBytes(&pszResponseBuf, m_dwLiteralInProgressBytesLeft, &cbRead); if (FAILED(hrASyncResult)) return hrASyncResult; // Update our byte count Assert((DWORD)cbRead <= m_dwLiteralInProgressBytesLeft); m_dwLiteralInProgressBytesLeft -= cbRead; // Make note of received blob in log file if (m_pLogFile) { char szLogLine[CMDLINE_BUFSIZE]; wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Buffer (literal) of length %i", cbRead); m_pLogFile->WriteLog(LOGFILE_RX, szLogLine); } } // else-not line mode // Process it switch (m_irsState) { case irsUNINITIALIZED: AssertSz(FALSE, "Attempted to use Imap4Agent class without initializing"); SafeMemFree(pszResponseBuf); break; case irsNOT_CONNECTED: AssertSz(FALSE, "Received response from server when not connected"); SafeMemFree(pszResponseBuf); break; case irsSVR_GREETING: ProcessServerGreeting(pszResponseBuf, cbRead); break; case irsIDLE: { IMAP_RESPONSE_ID irParseResult; CheckForCompleteResponse(pszResponseBuf, cbRead, &irParseResult); // Check for unsolicited BYE response, and notify user of error // Solicited BYE responses (eg, during LOGOUT cmd) can be ignored if (irBYE_RESPONSE == irParseResult && IXP_AUTHRETRY != m_status && IXP_DISCONNECTING != m_status && IXP_DISCONNECTED != m_status) { char szFailureText[MAX_RESOURCESTRING]; // Looks like an unsolicited BYE response to me // Drop connection to avoid IXP_E_CONNECTION_DROPPED err DropConnection(); // Report to user (sometimes server provides useful error text) LoadString(g_hLocRes, idsIMAPUnsolicitedBYE, szFailureText, ARRAYSIZE(szFailureText)); OnIMAPError(IXP_E_IMAP_UNSOLICITED_BYE, szFailureText, USE_LAST_RESPONSE); } } // case irsIDLE break; case irsLITERAL: AddBytesToLiteral(pszResponseBuf, cbRead); break; case irsFETCH_BODY: DispatchFetchBodyPart(pszResponseBuf, cbRead, fFREE_BODY_TAG); SafeMemFree(pszResponseBuf); break; default: AssertSz(FALSE, "Unhandled receiver state in ProcessResponseLine()"); SafeMemFree(pszResponseBuf); break; } // switch (m_irsState) return hrASyncResult; } // ProcessResponseLine //*************************************************************************** // Function: ProcessSendQueue // // Purpose: // This function is responsible for all transmissions from the client to // the IMAP server. It is called when certain events occur, such as the // receipt of the AE_SENDDONE event in OnNotify(). // // Arguments: // IMAP_SEND_EVENT iseEvent [in] - the send event which just occurred, // such as iseSEND_COMMAND (used to initiate a command) or // iseCMD_CONTINUATION (when command continuation response received from // the IMAP server). //*************************************************************************** void CImap4Agent::ProcessSendQueue(IMAP_SEND_EVENT iseEvent) { boolean bFreeToSendLiteral, bFreeToUnpause; IMAP_LINE_FRAGMENT *pilfNextFragment; Assert(m_lRefCount > 0); Assert(ssNotConnected < m_ssServerState); Assert(irsNOT_CONNECTED < m_irsState); // Initialize variables bFreeToSendLiteral = FALSE; bFreeToUnpause = FALSE; // Peek at current fragment EnterCriticalSection(&m_cs); // Reserve this NOW to avoid deadlock EnterCriticalSection(&m_csSendQueue); GetNextCmdToSend(); if (NULL != m_piciCmdInSending) pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment; else pilfNextFragment = NULL; // Act on the IMAP send event posted to us switch (iseEvent) { case iseSEND_COMMAND: case iseSENDDONE: // We don't have to do anything special for these events break; case iseCMD_CONTINUATION: // Received command continuation from IMAP server. We are free to send literal bFreeToSendLiteral = TRUE; Assert(NULL != pilfNextFragment && iltLITERAL == pilfNextFragment->iltFragmentType); break; case iseUNPAUSE: bFreeToUnpause = TRUE; IxpAssert(NULL != pilfNextFragment && iltPAUSE == pilfNextFragment->iltFragmentType); break; default: AssertSz(FALSE, "Received unknown IMAP send event"); break; } // Send as many fragments as we can. We must stop sending if: // a) Any AsyncConn send command returns hrWouldBlock. // b) The send queue is empty // c) Next fragment is a literal and we don't have cmd continuation from svr // d) We are at a iltPAUSE fragment, and we don't have the go-ahead to unpause // e) We are at a iltSTOP fragment. while (TRUE == m_bFreeToSend && NULL != pilfNextFragment && ((iltLITERAL != pilfNextFragment->iltFragmentType) || TRUE == bFreeToSendLiteral) && ((iltPAUSE != pilfNextFragment->iltFragmentType) || TRUE == bFreeToUnpause) && (iltSTOP != pilfNextFragment->iltFragmentType)) { HRESULT hrResult; int iNumBytesSent; IMAP_LINE_FRAGMENT *pilf; // We are free to send the next fragment, whether it's a line, literal or rangelist // Put us into busy mode to enable the watchdog timer if (FALSE == m_fBusy) { hrResult = HrEnterBusy(); Assert(SUCCEEDED(hrResult)); // In retail, we want to try to continue even if HrEnterBusy failed. } // Send next fragment (have to check if stored as a string or a stream) pilfNextFragment = pilfNextFragment->pilfNextFragment; // Peek at next frag pilf = DequeueFragment(m_piciCmdInSending->pilqCmdLineQueue); // Get current frag if (iltPAUSE == pilf->iltFragmentType) { hrResult = S_OK; // Do nothing } else if (iltSTOP == pilf->iltFragmentType) { AssertSz(FALSE, "What are we doing trying to process a STOP?"); hrResult = S_OK; // Do nothing } else if (iltRANGELIST == pilf->iltFragmentType) { AssertSz(FALSE, "All rangelists should have been coalesced!"); hrResult = S_OK; // Do nothing } else if (ilsSTRING == pilf->ilsLiteralStoreType) { hrResult = m_pSocket->SendBytes(pilf->data.pszSource, pilf->dwLengthOfFragment, &iNumBytesSent); // Record sent line in log file if (m_pLogFile) { // Hide the LOGIN command from logfile, for security reasons if (icLOGIN_COMMAND != m_piciCmdInSending->icCommandID) m_pLogFile->WriteLog(LOGFILE_TX, pilf->data.pszSource); else m_pLogFile->WriteLog(LOGFILE_TX, "LOGIN command sent"); } } else if (ilsSTREAM == pilf->ilsLiteralStoreType) { char szLogLine[128]; // No need to rewind stream - CAsyncConn::SendStream does it for us hrResult = m_pSocket->SendStream(pilf->data.pstmSource, &iNumBytesSent); // Record stream size in log file wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Stream of length %lu", pilf->dwLengthOfFragment); if (m_pLogFile) m_pLogFile->WriteLog(LOGFILE_TX, szLogLine); // Record stream size for progress indication if (icAPPEND_COMMAND == m_piciCmdInSending->icCommandID) { m_dwAppendStreamUploaded = 0; m_dwAppendStreamTotal = pilf->dwLengthOfFragment; UploadStreamProgress(iNumBytesSent); } } else { AssertSz(FALSE, "What is in my send queue?"); hrResult = S_OK; // Ignore it and try to continue } // Clean up variables after the send bFreeToSendLiteral = FALSE; // We've used up the cmd continuation bFreeToUnpause = FALSE; // We've used this up, too FreeFragment(&pilf); // Handle errors in sending. // If either send command returns hrWouldBlock, this means we cannot send // more data until we receive an AE_SENDDONE event from CAsyncConn. if (IXP_E_WOULD_BLOCK == hrResult) { m_bFreeToSend = FALSE; hrResult = S_OK; // $REVIEW: TEMPORARY until EricAn makes hrWouldBlock a success code } else if (FAILED(hrResult)) { IMAP_RESPONSE irIMAPResponse; char szFailureText[MAX_RESOURCESTRING]; // Send error: Report this command as terminated irIMAPResponse.wParam = m_piciCmdInSending->wParam; irIMAPResponse.lParam = m_piciCmdInSending->lParam; irIMAPResponse.hrResult = hrResult; LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText, ARRAYSIZE(szFailureText)); irIMAPResponse.lpszResponseText = szFailureText; irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION; OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse); } // Are we finished with the current command? if (NULL == pilfNextFragment || FAILED(hrResult)) { CIMAPCmdInfo *piciFinishedCmd; // Dequeue current command from send queue piciFinishedCmd = DequeueCommand(); if (NULL != piciFinishedCmd) { if (SUCCEEDED(hrResult)) { // We successfully finished sending current command. Put it in // list of commands waiting for a server response AddPendingCommand(piciFinishedCmd); Assert(NULL == pilfNextFragment); } else { // Failed commands don't deserve to live delete piciFinishedCmd; pilfNextFragment = NULL; // No longer valid // Drop out of busy mode AssertSz(m_fBusy, "Check your logic, I'm calling LeaveBusy " "although not in a busy state!"); LeaveBusy(); } } else { // Hey, someone pulled the rug out! AssertSz(FALSE, "I had this cmd... and now it's GONE!"); } } // if (NULL == pilfNextFragment || FAILED(hrResult)) // If we finished sending current cmd, set us up to send next command if (NULL == pilfNextFragment && NULL != m_piciSendQueue) { GetNextCmdToSend(); if (NULL != m_piciCmdInSending) pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment; } } // while LeaveCriticalSection(&m_csSendQueue); LeaveCriticalSection(&m_cs); } // ProcessSendQueue //*************************************************************************** // Function: GetNextCmdToSend // // Purpose: // This function leaves a pointer to the next command to send, in // m_piciCmdInSending. If m_piciCmdInSending is already non-NULL (indicating // a command in progress), then this function does nothing. Otherwise, this // function chooses the next command from m_piciSendQueue using a set of // rules described within. //*************************************************************************** void CImap4Agent::GetNextCmdToSend(void) { CIMAPCmdInfo *pici; // First check if we're connected if (IXP_CONNECTED != m_status && IXP_AUTHORIZING != m_status && IXP_AUTHRETRY != m_status && IXP_AUTHORIZED != m_status && IXP_DISCONNECTING != m_status) { Assert(NULL == m_piciCmdInSending); return; } // Check if we're already in the middle of sending a command if (NULL != m_piciCmdInSending) return; // Loop through the send queue looking for next eligible candidate to send pici = m_piciSendQueue; while (NULL != pici) { IMAP_COMMAND icCurrentCmd; // For a command to be sent, it must meet the following criteria: // (1) The server must be in the correct server state. Authenticated cmds such // as SELECT must wait until non-Authenticated cmds like LOGIN are complete. // (2) Commands for which we want to guarantee proper wParam, lParam for their // untagged responses cannot be streamed. See CanStreamCommand for details. // (3) If the command is NON-UID FETCH/STORE/SEARCH or COPY, then all pending // cmds must be NON-UID FETCH/STORE/SEARCH. icCurrentCmd = pici->icCommandID; if (m_ssServerState >= pici->ssMinimumState && CanStreamCommand(icCurrentCmd)) { if ((icFETCH_COMMAND == icCurrentCmd || icSTORE_COMMAND == icCurrentCmd || icSEARCH_COMMAND == icCurrentCmd || icCOPY_COMMAND == icCurrentCmd) && FALSE == pici->fUIDRangeList) { if (isValidNonWaitingCmdSequence()) break; // This command is good to go } else break; // This command is good to go } // Advance ptr to next command pici = pici->piciNextCommand; } // while // If we found a command, coalesce its iltLINE and iltRANGELIST elements if (NULL != pici) { CompressCommand(pici); m_piciCmdInSending = pici; } } // GetNextCmdToSend //*************************************************************************** // Function: CanStreamCommand // // Purpose: // This function determines whether or not the given command can be // streamed. All commands can be streamed except for the following: // SELECT, EXAMINE, LIST, LSUB and SEARCH. // // SELECT and EXAMINE cannot be streamed because it doesn't make much sense // to allow that. // LIST, LSUB and SEARCH cannot be streamed because we want to guarantee // that we can identify the correct wParam, lParam and pCBHandler when we call // OnResponse for their untagged responses. // // Arguments: // IMAP_COMMAND icCommandID [in] - the command which you would like to send // to the server. // // Returns: // TRUE if the given command may be sent. FALSE if you cannot send the // given command at this time (try again later). //*************************************************************************** boolean CImap4Agent::CanStreamCommand(IMAP_COMMAND icCommandID) { boolean fResult; WORD wNumberOfMatches; fResult = TRUE; wNumberOfMatches = 0; switch (icCommandID) { // We don't stream any of the following commands case icSELECT_COMMAND: case icEXAMINE_COMMAND: wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icSELECT_COMMAND, icEXAMINE_COMMAND); break; case icLIST_COMMAND: wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLIST_COMMAND); break; case icLSUB_COMMAND: wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLSUB_COMMAND); break; case icSEARCH_COMMAND: wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icSEARCH_COMMAND); break; } //switch if (wNumberOfMatches > 0) fResult = FALSE; return fResult; } // CanStreamCommand //*************************************************************************** // Function: isValidNonWaitingCmdSequence // // Purpose: // This function is called whenever we would like to send a FETCH, STORE, // SEARCH or COPY command (all NON-UID) to the server. These commands are // subject to waiting rules as discussed in section 5.5 of RFC2060. // // Returns: // TRUE if the non-UID FETCH/STORE/SEARCH/COPY command can be sent at // this time. FALSE if the command cannot be sent at this time (try again // later). //*************************************************************************** boolean CImap4Agent::isValidNonWaitingCmdSequence(void) { CIMAPCmdInfo *pici; boolean fResult; // Loop through the list of pending commands pici = m_piciPendingList; fResult = TRUE; while (NULL != pici) { IMAP_COMMAND icCurrentCmd; // non-UID FETCH/STORE/SEARCH/COPY can only execute if the only // pending commands are non-UID FETCH/STORE/SEARCH. icCurrentCmd = pici->icCommandID; if (icFETCH_COMMAND != icCurrentCmd && icSTORE_COMMAND != icCurrentCmd && icSEARCH_COMMAND != icCurrentCmd || pici->fUIDRangeList) { fResult = FALSE; break; } // Advance pointer to next command pici = pici->piciNextCommand; } // while return fResult; } // isValidNonWaitingCmdSequence //*************************************************************************** // Function: CompressCommand // // Purpose: This function walks through the given command's linefrag queue // and combines all sequential iltLINE and iltRANGELIST linefrag elements // into a single iltLINE element for transmitting purposes. The reason we // have to combine these is because I had a pipe dream once that CImap4Agent // would auto-detect EXPUNGE responses and modify all iltRANGELIST elements // in m_piciSendQueue to reflect the new msg seq num reality. Who knows, // it might even come true some day. // // When it does come true, this function can still exist: once a command // enters m_piciCmdInSending, it is too late to modify its rangelist. // // Arguments: // CIMAPCmdInfo *pici [in] - pointer to the IMAP command to compress. //*************************************************************************** void CImap4Agent::CompressCommand(CIMAPCmdInfo *pici) { IMAP_LINE_FRAGMENT *pilfCurrent, *pilfStartOfRun, *pilfPreStartOfRun; HRESULT hrResult; // Codify assumptions Assert(NULL != pici); Assert(5 == iltLAST); // If this changes, update this function // Initialize variables hrResult = S_OK; pilfCurrent = pici->pilqCmdLineQueue->pilfFirstFragment; pilfStartOfRun = pilfCurrent; pilfPreStartOfRun = NULL; // Points to linefrag element before pilfStartOfRun while (1) { if (NULL == pilfCurrent || (iltLINE != pilfCurrent->iltFragmentType && iltRANGELIST != pilfCurrent->iltFragmentType)) { // We've hit a non-coalescable linefrag, coalesce previous run // We only coalesce runs which are greater than one linefrag element if (NULL != pilfStartOfRun && pilfCurrent != pilfStartOfRun->pilfNextFragment) { IMAP_LINE_FRAGMENT *pilf, *pilfSuperLine; CByteStream bstmCmdLine; // Run length > 1, coalesce the entire run pilf = pilfStartOfRun; while (pilf != pilfCurrent) { if (iltLINE == pilf->iltFragmentType) { hrResult = bstmCmdLine.Write(pilf->data.pszSource, pilf->dwLengthOfFragment, NULL); if (FAILED(hrResult)) goto exit; } else { LPSTR pszMsgRange; DWORD dwLengthOfString; // Convert rangelist to string Assert(iltRANGELIST == pilf->iltFragmentType); hrResult = pilf->data.prlRangeList-> RangeToIMAPString(&pszMsgRange, &dwLengthOfString); if (FAILED(hrResult)) goto exit; hrResult = bstmCmdLine.Write(pszMsgRange, dwLengthOfString, NULL); MemFree(pszMsgRange); if (FAILED(hrResult)) goto exit; // Append a space behind the rangelist hrResult = bstmCmdLine.Write(g_szSpace, 1, NULL); if (FAILED(hrResult)) goto exit; } // else pilf = pilf->pilfNextFragment; } // while (pilf != pilfCurrent) // OK, now we've coalesced the run data into a stream // Create a iltLINE fragment to hold the super-string pilfSuperLine = new IMAP_LINE_FRAGMENT; if (NULL == pilfSuperLine) { hrResult = E_OUTOFMEMORY; goto exit; } pilfSuperLine->iltFragmentType = iltLINE; pilfSuperLine->ilsLiteralStoreType = ilsSTRING; hrResult = bstmCmdLine.HrAcquireStringA(&pilfSuperLine->dwLengthOfFragment, &pilfSuperLine->data.pszSource, ACQ_DISPLACE); if (FAILED(hrResult)) { delete pilfSuperLine; goto exit; } // OK, we've created the uber-line, now link it into list pilfSuperLine->pilfNextFragment = pilfCurrent; pilfSuperLine->pilfPrevFragment = pilfPreStartOfRun; Assert(pilfPreStartOfRun == pilfStartOfRun->pilfPrevFragment); if (NULL == pilfPreStartOfRun) // Insert at head of queue pici->pilqCmdLineQueue->pilfFirstFragment = pilfSuperLine; else pilfPreStartOfRun->pilfNextFragment = pilfSuperLine; // Special case: if pilfCurrent is NULL, pilfSuperLine is new last frag if (NULL == pilfCurrent) pici->pilqCmdLineQueue->pilfLastFragment = pilfSuperLine; // Free the old run of linefrag elements pilf = pilfStartOfRun; while(pilf != pilfCurrent) { IMAP_LINE_FRAGMENT *pilfNext; pilfNext = pilf->pilfNextFragment; FreeFragment(&pilf); pilf = pilfNext; } // while(pilf != pilfCurrent) } // if run length > 1 // Start collecting line fragments for next coalescing run if (NULL != pilfCurrent) { pilfStartOfRun = pilfCurrent->pilfNextFragment; pilfPreStartOfRun = pilfCurrent; } // if } // if current linefrag is non-coalescable // Advance to next line fragment if (NULL != pilfCurrent) pilfCurrent = pilfCurrent->pilfNextFragment; else break; // Our work here is done } // while (NULL != pilfCurrent) exit: AssertSz(SUCCEEDED(hrResult), "Could not compress an IMAP command"); } // CompressCommand //*************************************************************************** // Function: SendCmdLine // // Purpose: // This function enqueues an IMAP line fragment (as opposed to an IMAP // literal fragment) on the send queue of the given CIMAPCmdInfo structure. // The insertion point can either be in front // All IMAP commands are constructed in full before being submitted to the // send machinery, so this function does not actually transmit anything. // // Arguments: // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing // the IMAP command currently under construction. // DWORD dwFlags [in] - various options: // sclINSERT_BEFORE_PAUSE: line fragment will be inserted before the // first iltPAUSE fragment in the queue. It is the // caller's responsibility to ensure that a iltPAUSE // fragment exists. // sclAPPEND_TO_END: (DEFAULT CASE, there is no flag for this) line fragment // will be appended to the end of the queue. // sclAPPEND_CRLF: Appends CRLF to contents of lpszCommandText when // constructing the line fragment. // // LPCSTR lpszCommandText [in] - a pointer to the line fragment to enqueue. // The first line fragment of all commands should include a tag. This // function does not provide command tags, and does not append CRLF to // the end of each line by default (see sclAPPEND_CRLF above). // DWORD dwCmdLineLength [in] - the length of the text pointed to by // lpszCommandText. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::SendCmdLine(CIMAPCmdInfo *piciCommand, DWORD dwFlags, LPCSTR lpszCommandText, DWORD dwCmdLineLength) { IMAP_LINE_FRAGMENT *pilfLine; BOOL bResult; BOOL fAppendCRLF; Assert(m_lRefCount > 0); Assert(NULL != piciCommand); Assert(NULL != lpszCommandText); // Create and fill out a line fragment element fAppendCRLF = !!(dwFlags & sclAPPEND_CRLF); pilfLine = new IMAP_LINE_FRAGMENT; if (NULL == pilfLine) return E_OUTOFMEMORY; pilfLine->iltFragmentType = iltLINE; pilfLine->ilsLiteralStoreType = ilsSTRING; pilfLine->dwLengthOfFragment = dwCmdLineLength + (fAppendCRLF ? 2 : 0); pilfLine->pilfNextFragment = NULL; pilfLine->pilfPrevFragment = NULL; DWORD cchSize = (pilfLine->dwLengthOfFragment + 1); // Room for null-term bResult = MemAlloc((void **)&pilfLine->data.pszSource, cchSize * sizeof(pilfLine->data.pszSource[0])); if (FALSE == bResult) { delete pilfLine; return E_OUTOFMEMORY; } CopyMemory(pilfLine->data.pszSource, lpszCommandText, dwCmdLineLength); if (fAppendCRLF) StrCpyN(pilfLine->data.pszSource + dwCmdLineLength, c_szCRLF, cchSize - dwCmdLineLength); else *(pilfLine->data.pszSource + dwCmdLineLength) = '\0'; // Null-terminate the line // Queue it up if (dwFlags & sclINSERT_BEFORE_PAUSE) { InsertFragmentBeforePause(pilfLine, piciCommand->pilqCmdLineQueue); ProcessSendQueue(iseSEND_COMMAND); // Pump send queue in this case } else EnqueueFragment(pilfLine, piciCommand->pilqCmdLineQueue); return S_OK; } // SendCmdLine //*************************************************************************** // Function: SendLiteral // // Purpose: // This function enqueues an IMAP literal fragment (as opposed to an IMAP // line fragment) on the send queue of the given CIMAPCmdInfo structure. // All IMAP commands are constructed in full before being submitted to the // send machinery, so this function does not actually transmit anything. // // Arguments: // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing // the IMAP command currently under construction. // LPSTREAM pstmLiteral [in] - a pointer to the stream containing the // literal to be sent. // DWORD dwSizeOfStream [in] - the size of the stream. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::SendLiteral(CIMAPCmdInfo *piciCommand, LPSTREAM pstmLiteral, DWORD dwSizeOfStream) { IMAP_LINE_FRAGMENT *pilfLiteral; Assert(m_lRefCount > 0); Assert(NULL != pstmLiteral); // Create and fill out a fragment structure for the literal pilfLiteral = new IMAP_LINE_FRAGMENT; if (NULL == pilfLiteral) return E_OUTOFMEMORY; pilfLiteral->iltFragmentType = iltLITERAL; pilfLiteral->ilsLiteralStoreType = ilsSTREAM; pilfLiteral->dwLengthOfFragment = dwSizeOfStream; pstmLiteral->AddRef(); // We're about to make a copy of this pilfLiteral->data.pstmSource = pstmLiteral; pilfLiteral->pilfNextFragment = NULL; pilfLiteral->pilfPrevFragment = NULL; // Queue it up to send out when we receive command continuation from svr EnqueueFragment(pilfLiteral, piciCommand->pilqCmdLineQueue); return S_OK; } // SendLiteral //*************************************************************************** // Function: SendRangelist // // Purpose: // This function enqueues a rangelist on the send queue of the given // CIMAPCmdInfo structure. All IMAP commands are constructed in full before // being submitted to the send machinery, so this function does not actually // transmit anything. The reason for storing rangelists is so that if the // rangelist represents a message sequence number range, we can resequence it // if we receive EXPUNGE responses before the command is transmitted. // // Arguments: // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing // the IMAP command currently under construction. // IRangeList *prlRangeList [in] - rangelist which will be converted to an // IMAP message set during command transmission. // boolean bUIDRangeList [in] - TRUE if rangelist represents a UID message // set, FALSE if it represents a message sequence number message set. // UID message sets are not subject to resequencing after an EXPUNGE // response is received from the server. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::SendRangelist(CIMAPCmdInfo *piciCommand, IRangeList *prlRangeList, boolean bUIDRangeList) { IMAP_LINE_FRAGMENT *pilfRangelist; Assert(m_lRefCount > 0); Assert(NULL != piciCommand); Assert(NULL != prlRangeList); // Create and fill out a rangelist element pilfRangelist = new IMAP_LINE_FRAGMENT; if (NULL == pilfRangelist) return E_OUTOFMEMORY; pilfRangelist->iltFragmentType = iltRANGELIST; pilfRangelist->ilsLiteralStoreType = ilsSTRING; pilfRangelist->dwLengthOfFragment = 0; pilfRangelist->pilfNextFragment = NULL; pilfRangelist->pilfPrevFragment = NULL; prlRangeList->AddRef(); pilfRangelist->data.prlRangeList = prlRangeList; // Queue it up EnqueueFragment(pilfRangelist, piciCommand->pilqCmdLineQueue); return S_OK; } // SendRangelist //*************************************************************************** // Function: SendPause // // Purpose: // This function enqueues a pause on the send queue of the given // CIMAPCmdInfo structure. All IMAP commands are constructed in full before // being submitted to the send machinery, so this function does not actually // transmit anything. A pause is used to freeze the send queue until we signal // it to proceed again. It is used in commands which involve bi-directional // communication, such as AUTHENTICATE or the IDLE extension. // // Arguments: // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing // the IMAP command currently under construction. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::SendPause(CIMAPCmdInfo *piciCommand) { IMAP_LINE_FRAGMENT *pilfPause; Assert(m_lRefCount > 0); Assert(NULL != piciCommand); // Create and fill out a pause element pilfPause = new IMAP_LINE_FRAGMENT; if (NULL == pilfPause) return E_OUTOFMEMORY; pilfPause->iltFragmentType = iltPAUSE; pilfPause->ilsLiteralStoreType = ilsSTRING; pilfPause->dwLengthOfFragment = 0; pilfPause->pilfNextFragment = NULL; pilfPause->pilfPrevFragment = NULL; pilfPause->data.pszSource = NULL; // Queue it up EnqueueFragment(pilfPause, piciCommand->pilqCmdLineQueue); return S_OK; } // SendPause //*************************************************************************** // Function: SendStop // // Purpose: // This function enqueues a STOP on the send queue of the given // CIMAPCmdInfo structure. All IMAP commands are constructed in full before // being submitted to the send machinery, so this function does not actually // transmit anything. A STOP is used to freeze the send queue until that // command is removed from the send queue by tagged command completion. // Currently only used in the IDLE command, because we don't want to send // any commands until the server indicates that we are out of IDLE mode. // // Arguments: // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing // the IMAP command currently under construction. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::SendStop(CIMAPCmdInfo *piciCommand) { IMAP_LINE_FRAGMENT *pilfStop; Assert(m_lRefCount > 0); Assert(NULL != piciCommand); // Create and fill out a stop element pilfStop = new IMAP_LINE_FRAGMENT; if (NULL == pilfStop) return E_OUTOFMEMORY; pilfStop->iltFragmentType = iltSTOP; pilfStop->ilsLiteralStoreType = ilsSTRING; pilfStop->dwLengthOfFragment = 0; pilfStop->pilfNextFragment = NULL; pilfStop->pilfPrevFragment = NULL; pilfStop->data.pszSource = NULL; // Queue it up EnqueueFragment(pilfStop, piciCommand->pilqCmdLineQueue); return S_OK; } // SendStop //*************************************************************************** // Function: ParseSvrResponseLine // // Purpose: // Given a line, this function classifies the line // as an untagged response, a command continuation, or a tagged response. // Depending on the classification, the line is then dispatched to helper // functions to parse and act on the line. // // Arguments: // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - IMAP line fragment to parse. // The given pointer is updated so that it always points to the last // IMAP line fragment processed. The caller need only free this fragment. // All previous fragments will have already been freed. // boolean *lpbTaggedResponse [out] - sets to TRUE if response is tagged. // LPSTR lpszTagFromSvr [out] - returns tag here if response was tagged. // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response, // if we recognized it. Otherwise returns irNONE. // // Returns: // HRESULT indicating success or failure. //*************************************************************************** HRESULT CImap4Agent::ParseSvrResponseLine (IMAP_LINE_FRAGMENT **ppilfLine, boolean *lpbTaggedResponse, LPSTR lpszTagFromSvr, IMAP_RESPONSE_ID *pirParseResult) { LPSTR p, lpszSvrResponseLine; HRESULT hrResult; // Check arguments Assert(m_lRefCount > 0); Assert(NULL != ppilfLine); Assert(NULL != *ppilfLine); Assert(NULL != lpbTaggedResponse); Assert(NULL != lpszTagFromSvr); Assert(NULL != pirParseResult); *lpbTaggedResponse = FALSE; // Assume untagged response to start *pirParseResult = irNONE; // Make sure we have a line fragment, not a literal if (iltLINE != (*ppilfLine)->iltFragmentType) { AssertSz(FALSE, "I was passed a literal to parse!"); return IXP_E_IMAP_RECVR_ERROR; } else lpszSvrResponseLine = (*ppilfLine)->data.pszSource; // Determine if server response was command continuation, untagged or tagged // Look at first character of response line to figure it out hrResult = S_OK; p = lpszSvrResponseLine + 1; switch(*lpszSvrResponseLine) { case cCOMMAND_CONTINUATION_PREFIX: if (NULL != m_piciCmdInSending && icAUTHENTICATE_COMMAND == m_piciCmdInSending->icCommandID) { LPSTR pszStartOfData; DWORD dwLengthOfData; if ((*ppilfLine)->dwLengthOfFragment <= 2) { pszStartOfData = NULL; dwLengthOfData = 0; } else { pszStartOfData = p + 1; dwLengthOfData = (*ppilfLine)->dwLengthOfFragment - 2; } AuthenticateUser(aeCONTINUE, pszStartOfData, dwLengthOfData); } else if (NULL != m_piciCmdInSending && icIDLE_COMMAND == m_piciCmdInSending->icCommandID) { // Leave busy mode, as we may be sitting idle for some time LeaveBusy(); m_fIDLE = TRUE; // We are now in IDLE mode // Check if any commands are waiting to be sent if ((NULL != m_piciCmdInSending) && (NULL != m_piciCmdInSending->piciNextCommand)) ProcessSendQueue(iseUNPAUSE); // Let's get out of IDLE } else { // Literal continuation response // Save response text - assume space follows "+", no big deal if it doesn't StrCpyN(m_szLastResponseText, p + 1, ARRAYSIZE(m_szLastResponseText)); ProcessSendQueue(iseCMD_CONTINUATION); // Go ahead and send the literal } break; // case cCOMMAND_CONTINUATION_PREFIX case cUNTAGGED_RESPONSE_PREFIX: if (cSPACE == *p) { // Server response fits spec'd form, parse as untagged response p += 1; // Advance p to point to next word // Untagged responses can be status, server/mailbox status or // message status responses. // Check for message status responses, first, by seeing // if first char of next word is a number // *** Consider using isdigit or IsDigit? *** // Assert(FALSE) (placeholder) if (*p >= '0' && *p <= '9') hrResult = ParseMsgStatusResponse(ppilfLine, p, pirParseResult); else { // It wasn't a msg status response, try status response hrResult = ParseStatusResponse(p, pirParseResult); // Check for error. The only error we ignore in this case is // IXP_E_IMAP_UNRECOGNIZED_RESP, since this only means we // should try to parse as a server/mailbox response if (FAILED(hrResult) && IXP_E_IMAP_UNRECOGNIZED_RESP != hrResult) break; if (irNONE == *pirParseResult) // It wasn't a status response, check if it's server/mailbox resp hrResult = ParseSvrMboxResponse(ppilfLine, p, pirParseResult); } } // if(cSPACE == *p) else // Must be a garbled response line hrResult = IXP_E_IMAP_SVR_SYNTAXERR; break; // case cUNTAGGED_RESPONSE_PREFIX default: // Assume it's a tagged response // Check if response line is big enough to hold one of our tags if ((*ppilfLine)->dwLengthOfFragment <= NUM_TAG_CHARS) { hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP; break; } // Skip past tag and check for the space p = lpszSvrResponseLine + NUM_TAG_CHARS; if (cSPACE == *p) { // Server response fits spec'd form, parse status response *p = '\0'; // Null-terminate at the tag, so we can retrieve it // Inform caller that this response was tagged, and return tag *lpbTaggedResponse = TRUE; StrCpyN(lpszTagFromSvr, lpszSvrResponseLine, TAG_BUFSIZE); // Now process and return status response hrResult = ParseStatusResponse(p + 1, pirParseResult); } else // Must be a garbled response line hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP; break; // case DEFAULT (assumed to be tagged) } // switch (*lpszSvrResponseLine) // If an error occurred, return contents of the last processed fragment if (FAILED(hrResult)) StrCpyN(m_szLastResponseText, (*ppilfLine)->data.pszSource, ARRAYSIZE(m_szLastResponseText)); return hrResult; } // ParseSvrResponseLine //*************************************************************************** // Function: ParseStatusResponse // // Purpose: // This function parses and acts on Status Responses (section 7.1 of // RFC-1730) (ie, OK/NO/BAD/PREAUTH/BYE). Response codes (eg, ALERT, // TRYCREATE) are dispatched to a helper function, ParseResponseCode, for // processing. The human-readable text associated with the response is // stored in the module variable, m_szLastResponseText. // // Arguments: // LPSTR lpszStatusResponseLine [in] - a pointer to the text which possibly // represents a status response. The text should not include the first // part of the line, which identifies the response as tagged ("0000 ") // or untagged ("* "). // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response, // if we recognized it. Otherwise does not write a value out. The caller // must initialize this variable to irNONE before calling this function. // // Returns: // HRESULT indicating success or failure. If this function identifies the // response as a status response, it returns S_OK. If it cannot recognize // the response, it returns IXP_E_IMAP_UNRECOGNIZED_RESP. If we recognize // the response but not the response CODE, it returns // IXP_S_IMAP_UNRECOGNIZED_RESP (success code because we don't want // to ever send an error to user based on unrecognized response code). //*************************************************************************** HRESULT CImap4Agent::ParseStatusResponse (LPSTR lpszStatusResponseLine, IMAP_RESPONSE_ID *pirParseResult) { HRESULT hrResult; LPSTR lpszResponseText; // Check arguments Assert(m_lRefCount > 0); Assert(NULL != lpszStatusResponseLine); Assert(NULL != pirParseResult); Assert(irNONE == *pirParseResult); hrResult = S_OK; // We can distinguish between all status responses by looking at second char // First, determine that string is at least 1 character long if ('\0' == *lpszStatusResponseLine) return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a status response that we understand lpszResponseText = lpszStatusResponseLine; switch (*(lpszStatusResponseLine+1)) { int iResult; case 'k': case 'K': // Possibly the "OK" Status response iResult = StrCmpNI(lpszStatusResponseLine, "OK ", 3); if (0 == iResult) { // Definitely an "OK" status response *pirParseResult = irOK_RESPONSE; lpszResponseText += 3; } break; // case 'K' for possible "OK" case 'o': case 'O': // Possibly the "NO" status response iResult = StrCmpNI(lpszStatusResponseLine, "NO ", 3); if (0 == iResult) { // Definitely a "NO" response *pirParseResult = irNO_RESPONSE; lpszResponseText += 3; } break; // case 'O' for possible "NO" case 'a': case 'A': // Possibly the "BAD" status response iResult = StrCmpNI(lpszStatusResponseLine, "BAD ", 4); if (0 == iResult) { // Definitely a "BAD" response *pirParseResult = irBAD_RESPONSE; lpszResponseText += 4; } break; // case 'A' for possible "BAD" case 'r': case 'R': // Possibly the "PREAUTH" status response iResult = StrCmpNI(lpszStatusResponseLine, "PREAUTH ", 8); if (0 == iResult) { // Definitely a "PREAUTH" response: // PREAUTH is issued only as a greeting - check for proper context // If improper context, ignore PREAUTH response if (ssConnecting == m_ssServerState) { *pirParseResult = irPREAUTH_RESPONSE; lpszResponseText += 8; m_ssServerState = ssAuthenticated; } } break; // case 'R' for possible "PREAUTH" case 'y': case 'Y': // Possibly the "BYE" status response iResult = StrCmpNI(lpszStatusResponseLine, "BYE ", 4); if (0 == iResult) { // Definitely a "BYE" response: // Set server state to not connected *pirParseResult = irBYE_RESPONSE; lpszResponseText += 4; m_ssServerState = ssNotConnected; } break; // case 'Y' for possible "BYE" } // switch (*(lpszStatusResponseLine+1)) // If we recognized the command, proceed to process the response code if (SUCCEEDED(hrResult) && irNONE != *pirParseResult) { // We recognized the command, so lpszResponseText points to resp_text // as defined in RFC-1730. Look for optional response code if ('[' == *lpszResponseText) { HRESULT hrResponseCodeResult; hrResponseCodeResult = ParseResponseCode(lpszResponseText + 1); if (FAILED(hrResponseCodeResult)) hrResult = hrResponseCodeResult; } else // No response code, record response text for future retrieval StrCpyN(m_szLastResponseText, lpszResponseText, ARRAYSIZE(m_szLastResponseText)); } // If we didn't recognize the command, translate hrResult if (SUCCEEDED(hrResult) && irNONE == *pirParseResult) hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP; return hrResult; } // ParseStatusResponse //*************************************************************************** // Function: ParseResponseCode // // Purpose: // This function parses and acts on the response code which may be // returned with a status response (eg, PERMANENTFLAGS or ALERT). It is // called by ParseStatusResponse upon detection of a response code. This // function saves the human-readable text of the response code to // m_szLastResponseLine. // // Arguments: // LPSTR lpszResponseCode [in] - a pointer to the response code portion // of a response line, omitting the opening bracket ("["). // // Returns: // HRESULT indicating success or failure. If we cannot recognize the // response code, we return IXP_S_IMAP_UNRECOGNIZED_RESP. //*************************************************************************** HRESULT CImap4Agent::ParseResponseCode(LPSTR lpszResponseCode) { HRESULT hrResult; WORD wHashValue; // Check arguments Assert(m_lRefCount > 0); Assert(NULL != lpszResponseCode); hrResult = IXP_S_IMAP_UNRECOGNIZED_RESP; switch (*lpszResponseCode) { int iResult; case 'A': case 'a': // Possibly the "ALERT" response code iResult = StrCmpNI(lpszResponseCode, "ALERT] ", 7); if (0 == iResult) { IMAP_RESPONSE irIMAPResponse; // Definitely the "ALERT" response code: irIMAPResponse.wParam = 0; irIMAPResponse.lParam = 0; irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = lpszResponseCode + 7; irIMAPResponse.irtResponseType = irtSERVER_ALERT; OnIMAPResponse(m_pCBHandler, &irIMAPResponse); hrResult = S_OK; break; } // *** FALL THROUGH *** to default case case 'P': case 'p': // Possibly the "PARSE" or "PERMANENTFLAGS" response code iResult = StrCmpNI(lpszResponseCode, "PERMANENTFLAGS ", 15); if (0 == iResult) { IMAP_MSGFLAGS PermaFlags; LPSTR p; DWORD dwNumBytesRead; // Definitely the "PERMANENTFLAGS" response code: // Parse flag list p = lpszResponseCode + 15; // p now points to start of flag list hrResult = ParseMsgFlagList(p, &PermaFlags, &dwNumBytesRead); if (SUCCEEDED(hrResult)) { IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Record response text p += dwNumBytesRead + 3; // p now points to response text StrCpyN(m_szLastResponseText, p, ARRAYSIZE(m_szLastResponseText)); GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, irPERMANENTFLAGS_RESPONSECODE); irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtPERMANENT_FLAGS; irIMAPResponse.irdResponseData.imfImapMessageFlags = PermaFlags; OnIMAPResponse(pCBHandler, &irIMAPResponse); } break; } // end of PERMANENTFLAGS response code iResult = StrCmpNI(lpszResponseCode, "PARSE] ", 7); if (0 == iResult) { IMAP_RESPONSE irIMAPResponse; // Definitely the "PARSE" response code: irIMAPResponse.wParam = 0; irIMAPResponse.lParam = 0; irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = lpszResponseCode + 7; irIMAPResponse.irtResponseType = irtPARSE_ERROR; OnIMAPResponse(m_pCBHandler, &irIMAPResponse); hrResult = S_OK; break; } // end of PARSE response code // *** FALL THROUGH *** to default case case 'R': case 'r': // Possibly "READ-ONLY" or "READ-WRITE" response iResult = StrCmpNI(lpszResponseCode, "READ-WRITE] ", 12); if (0 == iResult) { IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Definitely the "READ-WRITE" response code: hrResult = S_OK; // Record this for enforcement purposes m_bCurrentMboxReadOnly = FALSE; // Record response text for future reference StrCpyN(m_szLastResponseText, lpszResponseCode + 12, ARRAYSIZE(m_szLastResponseText)); GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, irREADWRITE_RESPONSECODE); irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtREADWRITE_STATUS; irIMAPResponse.irdResponseData.bReadWrite = TRUE; OnIMAPResponse(pCBHandler, &irIMAPResponse); break; } // end of READ-WRITE response iResult = StrCmpNI(lpszResponseCode, "READ-ONLY] ", 11); if (0 == iResult) { IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Definitely the "READ-ONLY" response code: hrResult = S_OK; // Record this for enforcement purposes m_bCurrentMboxReadOnly = TRUE; // Record response text for future reference StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText)); GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, irREADONLY_RESPONSECODE); irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtREADWRITE_STATUS; irIMAPResponse.irdResponseData.bReadWrite = FALSE; OnIMAPResponse(pCBHandler, &irIMAPResponse); break; } // end of READ-ONLY response // *** FALL THROUGH *** to default case case 'T': case 't': // Possibly the "TRYCREATE" response iResult = StrCmpNI(lpszResponseCode, "TRYCREATE] ", 11); if (0 == iResult) { IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Definitely the "TRYCREATE" response code: hrResult = S_OK; StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText)); GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, irTRYCREATE_RESPONSECODE); irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtTRYCREATE; OnIMAPResponse(pCBHandler, &irIMAPResponse); break; } // *** FALL THROUGH *** to default case case 'U': case 'u': // Possibly the "UIDVALIDITY" or "UNSEEN" response codes iResult = StrCmpNI(lpszResponseCode, "UIDVALIDITY ", 12); if (0 == iResult) { LPSTR p, lpszEndOfNumber; IMAP_RESPONSE irIMAPResponse; IIMAPCallback *pCBHandler; // Definitely the "UIDVALIDITY" response code: hrResult = S_OK; // Return value to our caller so they can determine sync issues p = lpszResponseCode + 12; // p points to UID number lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket if (NULL == lpszEndOfNumber) { hrResult = IXP_E_IMAP_SVR_SYNTAXERR; break; } *lpszEndOfNumber = '\0'; // Null-terminate the number AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?"); StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText)); GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam, &pCBHandler, irUIDVALIDITY_RESPONSECODE); irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = m_szLastResponseText; irIMAPResponse.irtResponseType = irtUIDVALIDITY; irIMAPResponse.irdResponseData.dwUIDValidity = StrToUint(p); OnIMAPResponse(pCBHandler, &irIMAPResponse); break; } // end of UIDVALIDITY response code iResult = StrCmpNI(lpszResponseCode, "UNSEEN ", 7); if (0 == iResult) { LPSTR p, lpszEndOfNumber; IMAP_RESPONSE irIMAPResponse; MBOX_MSGCOUNT mcMsgCount; // Definitely the "UNSEEN" response code: hrResult = S_OK; // Record the returned number for reference during new mail DL p = lpszResponseCode + 7; // p now points to first unseen msg num lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket if (NULL == lpszEndOfNumber) { hrResult = IXP_E_IMAP_SVR_SYNTAXERR; break; } *lpszEndOfNumber = '\0'; // Null-terminate the number // Store response code for notification after command completion mcMsgCount = mcMsgCount_INIT; mcMsgCount.dwUnseen = StrToUint(p); mcMsgCount.bGotUnseenResponse = TRUE; irIMAPResponse.wParam = 0; irIMAPResponse.lParam = 0; irIMAPResponse.hrResult = S_OK; irIMAPResponse.lpszResponseText = NULL; // Not relevant here irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE; irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount; OnIMAPResponse(m_pCBHandler, &irIMAPResponse); AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?"); StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText)); break; } // end of UNSEEN response code // *** FALL THROUGH *** to default case default: StrCpyN(m_szLastResponseText, lpszResponseCode, ARRAYSIZE(m_szLastResponseText)); break; // Default case: response code not recognized } // switch(*lpszResponseCode) return hrResult; } // ParseResponseCode //*************************************************************************** // Function: ParseSvrMboxResponse // // Purpose: // This function parses and acts on Server and Mailbox Status Responses // from the IMAP server (see section 7.2 of RFC-1730) (eg, CAPABILITY and // SEARCH responses). // // Arguments: // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the IMAP line // fragment to parse. It is used to retrieve the literals sent with the // response. This pointer is updated so that it always points to the last // processed fragment. The caller need only free the last fragment. All // other fragments will already be freed when this function returns. // LPSTR lpszSvrMboxResponseLine [in] - a pointer to the svr/mbox response // line, omitting the first part of the line which identifies the response // as tagged ("0001 ") or untagged ("* "). // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response, // if we recognized it. Otherwise does not write a value out. The caller // must initialize this variable to irNONE before calling this function. // // Returns: // HRESULT indicating success or failure. If the response is not recognized, // this function returns IXP_E_IMAP_UNRECOGNIZED_RESP. //*************************************************************************** HRESULT CImap4Agent::ParseSvrMboxResponse (IMAP_LINE_FRAGMENT **ppilfLine, LPSTR lpszSvrMboxResponseLine, IMAP_RESPONSE_ID *pirParseResult) { LPSTR pszTok; HRESULT hrResult; // Check arguments Assert(m_lRefCount > 0); Assert(NULL != ppilfLine); Assert(NULL != *ppilfLine); Assert(NULL != lpszSvrMboxResponseLine); Assert(NULL != pirParseResult); Assert(irNONE == *pirParseResult); hrResult = S_OK; // We can ID all svr/mbox status responses by looking at second char // First, determine that the line is at least 1 character long if ('\0' == *lpszSvrMboxResponseLine) return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a svr/mbox response switch (*(lpszSvrMboxResponseLine+1)) { int iResult; case 'a': case 'A': // Possibly the "CAPABILITY" response iResult = StrCmpNI(lpszSvrMboxResponseLine, "CAPABILITY ", 11); if (0 == iResult) { LPSTR p; // Definitely a "CAPABILITY" response *pirParseResult = irCAPABILITY_RESPONSE; // Search for and record known capabilities, discard unknowns p = lpszSvrMboxResponseLine + 11; // p points to first cap. token pszTok = p; p = StrTokEx(&pszTok, g_szSpace); // p now points to next token while (NULL != p) { parseCapability(p); // Record capabilities which we recognize p = StrTokEx(&pszTok, g_szSpace); // Grab next capability token } } // if(0 == iResult) break; // case 'A' for possible "CAPABILITY" case 'i': case 'I': // Possibly the "LIST" response: iResult = StrCmpNI(lpszSvrMboxResponseLine, "LIST ", 5); if (0 == iResult) { // Definitely a "LIST" response *pirParseResult = irLIST_RESPONSE; hrResult = ParseListLsubResponse(ppilfLine, lpszSvrMboxResponseLine + 5, irLIST_RESPONSE); } // if (0 == iResult) break; // case 'I' for possible "LIST" case 's': case 'S': // Possibly the "LSUB" response: iResult = StrCmpNI(lpszSvrMboxResponseLine, "LSUB ", 5); if (0 == iResult) { // Definitely a "LSUB" response: *pirParseResult = irLSUB_RESPONSE; hrResult = ParseListLsubResponse(ppilfLine, lpszSvrMboxResponseLine + 5, irLSUB_RESPONSE); } // if (0 == iResult) break; // case 'S' for possible "LSUB" case 'e': case 'E': // Possibly the "SEARCH" response: iResult = StrCmpNI(lpszSvrMboxResponseLine, "SEARCH", 6); if (0 == iResult) { // Definitely a "SEARCH" response: *pirParseResult = irSEARCH_RESPONSE; // Response can be "* SEARCH" or "* SEARCH ". 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 = 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) "" 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 " 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 " 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: . // // 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: . 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: . // // 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: . // // 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