Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

10318 lines
393 KiB

  1. //***************************************************************************
  2. // IMAP4 Protocol Class Implementation (CImap4Agent)
  3. // Written by Raymond Cheng, 3/21/96
  4. //
  5. // This class allows its callers to use IMAP4 client commands without having
  6. // to parse incidental responses from the IMAP4 server (which may contain
  7. // information unrelated to the original command). For instance, during a
  8. // SEARCH command, the IMAP server may issue EXISTS and RECENT responses to
  9. // indicate the arrival of new mail.
  10. //
  11. // The user of this class first creates a connection by calling
  12. // Connect. It is the caller's responsibility to ensure that the
  13. // connection is not severed due to inactivity (autologout). The caller
  14. // can guard against this by periodically sending Noop's.
  15. //***************************************************************************
  16. //---------------------------------------------------------------------------
  17. // Includes
  18. //---------------------------------------------------------------------------
  19. #include "pch.hxx"
  20. #include <iert.h>
  21. #include "IMAP4.h"
  22. #include "range.h"
  23. #include "dllmain.h"
  24. #include "resource.h"
  25. #include "mimeole.h"
  26. #include <shlwapi.h>
  27. #include "strconst.h"
  28. #include "demand.h"
  29. // I chose the IInternetTransport from IIMAPTransport instead
  30. // of CIxpBase, because I override some of CIxpBase's IInternetTransport
  31. // implementations, and I want CImap4Agent's versions to take priority.
  32. #define THIS_IInternetTransport ((IInternetTransport *) (IIMAPTransport *) this)
  33. //---------------------------------------------------------------------------
  34. // Module Constants
  35. //---------------------------------------------------------------------------
  36. //---------------------------------------------------------------------------
  37. // Module Constants
  38. //---------------------------------------------------------------------------
  39. // *** Stolen from msgout.cpp! Find out how we can SHARE ***
  40. // Assert(FALSE); // Placeholder
  41. // The following is used to allow us to output dates in IMAP-compliant fashion
  42. static LPSTR lpszMonthsOfTheYear[] =
  43. {
  44. "Filler",
  45. "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  46. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  47. };
  48. const int TAG_BUFSIZE = NUM_TAG_CHARS + 1;
  49. const int MAX_RESOURCESTRING = 512;
  50. // IMAP Stuff
  51. const char cCOMMAND_CONTINUATION_PREFIX = '+';
  52. const char cUNTAGGED_RESPONSE_PREFIX = '*';
  53. const char cSPACE = ' ';
  54. const char c_szIMAP_MSG_ANSWERED[] = "Answered";
  55. const char c_szIMAP_MSG_FLAGGED[] = "Flagged";
  56. const char c_szIMAP_MSG_DELETED[] = "Deleted";
  57. const char c_szIMAP_MSG_DRAFT[] = "Draft";
  58. const char c_szIMAP_MSG_SEEN[] = "Seen";
  59. const char c_szDONE[] = "DONE\r\n";
  60. // *** Unless you can guarantee that g_szSPACE and c_szCRLF stay
  61. // *** US-ASCII, I'll use these. Assert(FALSE); (placeholder)
  62. // const char c_szCRLF[] = "\r\n";
  63. // const char g_szSpace[] = " ";
  64. const boolean TAGGED = TRUE;
  65. const boolean UNTAGGED = FALSE;
  66. const BOOL fFREE_BODY_TAG = TRUE;
  67. const BOOL fDONT_FREE_BODY_TAG = FALSE;
  68. const BOOL tamNEXT_AUTH_METHOD = TRUE;
  69. const BOOL tamCURRENT_AUTH_METHOD = FALSE;
  70. const BOOL rcASTRING_ARG = TRUE;
  71. const BOOL rcNOT_ASTRING_ARG = FALSE;
  72. // For use with SendCmdLine
  73. const DWORD sclAPPEND_TO_END = 0x00000000; // This option happens by default
  74. const DWORD sclINSERT_BEFORE_PAUSE = 0x00000001;
  75. const DWORD sclAPPEND_CRLF = 0x00000002;
  76. const DWORD dwLITERAL_THRESHOLD = 128; // On the conservative side
  77. const MBOX_MSGCOUNT mcMsgCount_INIT = {FALSE, 0L, FALSE, 0L, FALSE, 0L};
  78. const FETCH_BODY_PART FetchBodyPart_INIT = {0, NULL, 0, 0, 0, FALSE, NULL, 0, 0};
  79. const AUTH_STATUS AuthStatus_INIT = {asUNINITIALIZED, FALSE, 0, 0, {0}, {0}, NULL, 0};
  80. //---------------------------------------------------------------------------
  81. // Functions
  82. //---------------------------------------------------------------------------
  83. //***************************************************************************
  84. // Function: CImap4Agent (Constructor)
  85. //***************************************************************************
  86. CImap4Agent::CImap4Agent (void) : CIxpBase(IXP_IMAP)
  87. {
  88. DOUT("CImap4Agent - CONSTRUCTOR");
  89. // Initialize module variables
  90. m_ssServerState = ssNotConnected;
  91. m_dwCapabilityFlags = 0;
  92. *m_szLastResponseText = '\0';
  93. DllAddRef();
  94. m_lRefCount = 1;
  95. m_pCBHandler = NULL;
  96. m_irsState = irsUNINITIALIZED;
  97. m_bFreeToSend = TRUE;
  98. m_fIDLE = FALSE;
  99. m_ilqRecvQueue = ImapLinefragQueue_INIT;
  100. InitializeCriticalSection(&m_csTag);
  101. InitializeCriticalSection(&m_csSendQueue);
  102. InitializeCriticalSection(&m_csPendingList);
  103. m_pilfLiteralInProgress = NULL;
  104. m_dwLiteralInProgressBytesLeft = 0;
  105. m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT;
  106. m_dwAppendStreamUploaded = 0;
  107. m_dwAppendStreamTotal = 0;
  108. m_bCurrentMboxReadOnly = TRUE;
  109. m_piciSendQueue = NULL;
  110. m_piciPendingList = NULL;
  111. m_piciCmdInSending = NULL;
  112. m_pInternational = NULL;
  113. m_dwTranslateMboxFlags = IMAP_MBOXXLATE_DEFAULT;
  114. m_uiDefaultCP = GetACP(); // Must be default CP because we shipped like this
  115. m_asAuthStatus = AuthStatus_INIT;
  116. m_pdwMsgSeqNumToUID = NULL;
  117. m_dwSizeOfMsgSeqNumToUID = 0;
  118. m_dwHighestMsgSeqNum = 0;
  119. m_dwFetchFlags = 0;
  120. } // CImap4Agent
  121. //***************************************************************************
  122. // Function: ~CImap4Agent (Destructor)
  123. //***************************************************************************
  124. CImap4Agent::~CImap4Agent(void)
  125. {
  126. DOUT("CImap4Agent - DESTRUCTOR");
  127. Assert(0 == m_lRefCount);
  128. DropConnection(); // Ignore return result, since there's nothing we can do
  129. FreeAllData(E_FAIL); // General failure result, if cmds pending while destructor invoked
  130. DeleteCriticalSection(&m_csTag);
  131. DeleteCriticalSection(&m_csSendQueue);
  132. DeleteCriticalSection(&m_csPendingList);
  133. if (NULL != m_pInternational)
  134. m_pInternational->Release();
  135. if (NULL != m_pCBHandler)
  136. m_pCBHandler->Release();
  137. DllRelease();
  138. } // ~CImap4Agent
  139. //***************************************************************************
  140. // Function: QueryInterface
  141. //
  142. // Purpose:
  143. // Read the Win32SDK OLE Programming References (Interfaces) about the
  144. // IUnknown::QueryInterface function for details. This function returns a
  145. // pointer to the requested interface.
  146. //
  147. // Arguments:
  148. // REFIID iid [in] - an IID identifying the interface to return.
  149. // void **ppvObject [out] - if successful, this function returns a pointer
  150. // to the requested interface in this argument.
  151. //
  152. // Returns:
  153. // HRESULT indicating success or failure.
  154. //***************************************************************************
  155. HRESULT STDMETHODCALLTYPE CImap4Agent::QueryInterface(REFIID iid, void **ppvObject)
  156. {
  157. HRESULT hrResult;
  158. Assert(m_lRefCount > 0);
  159. Assert(NULL != ppvObject);
  160. // Init variables, check the arguments
  161. hrResult = E_NOINTERFACE;
  162. if (NULL == ppvObject) {
  163. hrResult = E_INVALIDARG;
  164. goto exit;
  165. }
  166. *ppvObject = NULL;
  167. // Find a ptr to the interface
  168. if (IID_IUnknown == iid) {
  169. // Choose the IIMAPTransport path to IUnknown over the other 3 paths
  170. // (all through CIxpBase) because this guarantees that CImap4Agent
  171. // provides the IUnknown implementation.
  172. *ppvObject = (IUnknown *) (IIMAPTransport *) this;
  173. ((IUnknown *) (IIMAPTransport *) this)->AddRef();
  174. }
  175. if (IID_IInternetTransport == iid) {
  176. *ppvObject = THIS_IInternetTransport;
  177. (THIS_IInternetTransport)->AddRef();
  178. }
  179. if (IID_IIMAPTransport == iid) {
  180. *ppvObject = (IIMAPTransport *) this;
  181. ((IIMAPTransport *) this)->AddRef();
  182. }
  183. if (IID_IIMAPTransport2 == iid) {
  184. *ppvObject = (IIMAPTransport2 *) this;
  185. ((IIMAPTransport2 *) this)->AddRef();
  186. }
  187. // Return success if we managed to snag an interface
  188. if (NULL != *ppvObject)
  189. hrResult = S_OK;
  190. exit:
  191. return hrResult;
  192. } // QueryInterface
  193. //***************************************************************************
  194. // Function: AddRef
  195. //
  196. // Purpose:
  197. // This function should be called whenever someone makes a copy of a
  198. // pointer to this object. It bumps the reference count so that we know
  199. // there is one more pointer to this object, and thus we need one more
  200. // release before we delete ourselves.
  201. //
  202. // Returns:
  203. // A ULONG representing the current reference count. Although technically
  204. // our reference count is signed, we should never return a negative number,
  205. // anyways.
  206. //***************************************************************************
  207. ULONG STDMETHODCALLTYPE CImap4Agent::AddRef(void)
  208. {
  209. Assert(m_lRefCount > 0);
  210. m_lRefCount += 1;
  211. DOUT ("CImap4Agent::AddRef, returned Ref Count=%ld", m_lRefCount);
  212. return m_lRefCount;
  213. } // AddRef
  214. //***************************************************************************
  215. // Function: Release
  216. //
  217. // Purpose:
  218. // This function should be called when a pointer to this object is to
  219. // go out of commission. It knocks the reference count down by one, and
  220. // automatically deletes the object if we see that nobody has a pointer
  221. // to this object.
  222. //
  223. // Returns:
  224. // A ULONG representing the current reference count. Although technically
  225. // our reference count is signed, we should never return a negative number,
  226. // anyways.
  227. //***************************************************************************
  228. ULONG STDMETHODCALLTYPE CImap4Agent::Release(void)
  229. {
  230. Assert(m_lRefCount > 0);
  231. m_lRefCount -= 1;
  232. DOUT("CImap4Agent::Release, returned Ref Count = %ld", m_lRefCount);
  233. if (0 == m_lRefCount) {
  234. delete this;
  235. return 0;
  236. }
  237. else
  238. return m_lRefCount;
  239. } // Release
  240. //***************************************************************************
  241. // Function: InitNew
  242. //
  243. // Purpose:
  244. // This function initializes the CImap4Agent class. This function
  245. // must be the next function called after instantiating the CImap4Agent class.
  246. //
  247. // Arguments:
  248. // LPSTR pszLogFilePath [in] - path to a log file (where all input and
  249. // output is logged), if the caller wishes to log IMAP transactions.
  250. // IIMAPCallback *pCBHandler [in] - pointer to a IIMAPCallback object.
  251. // This object allows the CImap4Agent class to report all IMAP response
  252. // results to its user.
  253. //
  254. // Returns:
  255. // HRESULT indicating success or failure.
  256. //***************************************************************************
  257. HRESULT STDMETHODCALLTYPE CImap4Agent::InitNew(LPSTR pszLogFilePath, IIMAPCallback *pCBHandler)
  258. {
  259. HRESULT hrResult;
  260. Assert(m_lRefCount > 0);
  261. Assert(ssNotConnected == m_ssServerState);
  262. Assert(irsUNINITIALIZED == m_irsState);
  263. Assert(NULL != pCBHandler);
  264. pCBHandler->AddRef();
  265. m_pCBHandler = pCBHandler;
  266. m_irsState = irsNOT_CONNECTED;
  267. hrResult = MimeOleGetInternat(&m_pInternational);
  268. if (FAILED(hrResult))
  269. return hrResult;
  270. return CIxpBase::OnInitNew("IMAP", pszLogFilePath, FILE_SHARE_READ,
  271. (ITransportCallback *)pCBHandler);
  272. } // InitNew
  273. //***************************************************************************
  274. // Function: SetDefaultCBHandler
  275. //
  276. // Purpose: This function changes the current default IIMAPCallback handler
  277. // to the given one.
  278. //
  279. // Arguments:
  280. // IIMAPCallback *pCBHandler [in] - a pointer to the new callback handler.
  281. //
  282. // Returns:
  283. // HRESULT indicating success or failure.
  284. //***************************************************************************
  285. HRESULT STDMETHODCALLTYPE CImap4Agent::SetDefaultCBHandler(IIMAPCallback *pCBHandler)
  286. {
  287. Assert(NULL != pCBHandler);
  288. if (NULL == pCBHandler)
  289. return E_INVALIDARG;
  290. if (NULL != m_pCBHandler)
  291. m_pCBHandler->Release();
  292. if (NULL != m_pCallback)
  293. m_pCallback->Release();
  294. m_pCBHandler = pCBHandler;
  295. m_pCBHandler->AddRef();
  296. m_pCallback = pCBHandler;
  297. m_pCallback->AddRef();
  298. return S_OK;
  299. } // SetDefaultCBHandler
  300. //***************************************************************************
  301. // Function: SetWindow
  302. //
  303. // Purpose:
  304. // This function creates the current window handle for async winsock process.
  305. //
  306. // Returns:
  307. // HRESULT indicating success or failure.
  308. //***************************************************************************
  309. HRESULT STDMETHODCALLTYPE CImap4Agent::SetWindow(void)
  310. {
  311. Assert(NULL != m_pSocket);
  312. return m_pSocket->SetWindow();
  313. } // SetWindow
  314. //***************************************************************************
  315. // Function: ResetWindow
  316. //
  317. // Purpose:
  318. // This function closes the current window handle for async winsock process.
  319. //
  320. // Returns:
  321. // HRESULT indicating success or failure.
  322. //***************************************************************************
  323. HRESULT STDMETHODCALLTYPE CImap4Agent::ResetWindow(void)
  324. {
  325. Assert(NULL != m_pSocket);
  326. return m_pSocket->ResetWindow();
  327. } // ResetWindow
  328. //***************************************************************************
  329. // Function: Connect
  330. //
  331. // Purpose:
  332. // This function is called to establish a connection with the IMAP server,
  333. // get its capabilities, and to authenticate the user.
  334. //
  335. // Arguments:
  336. // See explanation in imnxport.idl.
  337. //
  338. // Returns:
  339. // HRESULT indicating success or failure.
  340. //***************************************************************************
  341. HRESULT STDMETHODCALLTYPE CImap4Agent::Connect(LPINETSERVER pInetServer,
  342. boolean fAuthenticate,
  343. boolean fCommandLogging)
  344. {
  345. HRESULT hrResult;
  346. Assert(m_lRefCount > 0);
  347. Assert(ssAuthenticated > m_ssServerState);
  348. Assert(irsUNINITIALIZED < m_irsState);
  349. // We do not accept all combinations of argument: the caller cannot
  350. // perform his own authentication, and thus we MUST be responsible for
  351. // this. Even if PREAUTH is expected, we expect fAuthenticate to be TRUE.
  352. if (FALSE == fAuthenticate) {
  353. AssertSz(FALSE, "Current IIMAPTransport interface requires that fAuthenticate be TRUE.");
  354. return E_FAIL;
  355. }
  356. // Neither can we call the OnCommand callback
  357. if (fCommandLogging) {
  358. AssertSz(FALSE, "Current IIMAPTransport interface requires that fCommandLogging be FALSE.");
  359. return E_FAIL;
  360. }
  361. // Does user want us to always prompt for his password? Prompt him here to avoid
  362. // inactivity timeouts while the prompt is up. Do not prompt if password supplied.
  363. if (ISFLAGSET(pInetServer->dwFlags, ISF_ALWAYSPROMPTFORPASSWORD) &&
  364. '\0' == pInetServer->szPassword[0]) {
  365. if (NULL != m_pCallback)
  366. hrResult = m_pCallback->OnLogonPrompt(pInetServer, THIS_IInternetTransport);
  367. if (NULL == m_pCallback || S_OK != hrResult)
  368. return IXP_E_USER_CANCEL;
  369. }
  370. // If we reach this point, we need to establish a connection to IMAP server
  371. Assert(ssNotConnected == m_ssServerState);
  372. Assert(irsNOT_CONNECTED == m_irsState);
  373. hrResult = CIxpBase::Connect(pInetServer, fAuthenticate, fCommandLogging);
  374. if (SUCCEEDED(hrResult)) {
  375. m_irsState = irsSVR_GREETING;
  376. m_ssServerState = ssConnecting;
  377. }
  378. return hrResult;
  379. } // Connect
  380. //***************************************************************************
  381. // Function: ReLoginUser
  382. //
  383. // Purpose:
  384. // This function is called to re-attempt user authentication after a
  385. // failed attempt. It calls ITransportCallback::OnLogonPrompt to allow
  386. // the user to provide the correct logon information.
  387. //***************************************************************************
  388. void CImap4Agent::ReLoginUser(void)
  389. {
  390. HRESULT hrResult;
  391. char szFailureText[MAX_RESOURCESTRING];
  392. AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
  393. if (NULL == m_pCallback) {
  394. // We can't do a damned thing, drop connection (this can happen due to HandsOffCallback)
  395. DropConnection();
  396. return;
  397. }
  398. // Init variables
  399. szFailureText[0] = '\0';
  400. // First, put us in IXP_AUTHRETRY mode so that OnStatus is not called
  401. // for changes to the connection status
  402. OnStatus(IXP_AUTHRETRY);
  403. // OK, connection status is no longer being reported to the user
  404. // Ask the user for his stinking password
  405. hrResult = m_pCallback->OnLogonPrompt(&m_rServer, THIS_IInternetTransport);
  406. if (FAILED(hrResult) || S_FALSE == hrResult) {
  407. AssertSz(SUCCEEDED(hrResult), "OnLogonPrompt is supposed to return S_OK or S_FALSE!");
  408. DropConnection();
  409. goto exit;
  410. }
  411. // If we've reached this point, user hit the "OK" button
  412. // Check if we're still connected to the IMAP server
  413. if (irsNOT_CONNECTED < m_irsState) {
  414. // Still connected! Just try to authenticate
  415. LoginUser();
  416. }
  417. else {
  418. // Connect to server. We'll authenticate after connection established
  419. hrResult = Connect(&m_rServer, (boolean) !!m_fConnectAuth, (boolean) !!m_fCommandLogging);
  420. if (FAILED(hrResult))
  421. LoadString(g_hLocRes, idsConnectError, szFailureText,
  422. ARRAYSIZE(szFailureText));
  423. }
  424. exit:
  425. if (FAILED(hrResult)) {
  426. // Terminate login procedure and notify user
  427. OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
  428. }
  429. } // ReLoginUser
  430. //***************************************************************************
  431. // Function: Disconnect
  432. //
  433. // Purpose:
  434. // This function issues a LOGOUT command to the IMAP server and waits for
  435. // the server to process the LOGOUT command before dropping the connection.
  436. // This allows any currently executing commands to complete their execution.
  437. //
  438. // Returns:
  439. // HRESULT indicating success or failure.
  440. //***************************************************************************
  441. HRESULT STDMETHODCALLTYPE CImap4Agent::Disconnect(void)
  442. {
  443. return CIxpBase::Disconnect();
  444. } // Disconnect
  445. //***************************************************************************
  446. // Function: DropConnection
  447. //
  448. // Purpose:
  449. // This function issues a LOGOUT command to the IMAP server (if we
  450. // currently have nothing in the send queue), then drops the connection
  451. // before logout command completes.
  452. //
  453. // Returns:
  454. // HRESULT indicating success or failure.
  455. //***************************************************************************
  456. HRESULT STDMETHODCALLTYPE CImap4Agent::DropConnection(void)
  457. {
  458. Assert(m_lRefCount >= 0); // This function is called during destruction
  459. // You have to be connected to send a LOGOUT: ignore authorization states
  460. if (IXP_CONNECTED != m_status)
  461. goto exit; // Just close the CAsyncConn class
  462. // We send a logout command IF WE CAN, just as a courtesy. Our main goal
  463. // is to drop the connection, NOW.
  464. // If no commands in our send queue, send Logout command. Note that this
  465. // is no guarantee that CASyncConn is idle, but at least there's a chance
  466. if (NULL == m_piciCmdInSending ||
  467. (m_fIDLE && icIDLE_COMMAND == m_piciCmdInSending->icCommandID &&
  468. iltPAUSE == m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment->iltFragmentType)) {
  469. HRESULT hrLogoutResult;
  470. const char cszLogoutCmd[] = "ZZZZ LOGOUT\r\n";
  471. char sz[ARRAYSIZE(cszLogoutCmd) + ARRAYSIZE(c_szDONE)]; // Bigger than I need, but who cares
  472. int iNumBytesSent, iStrLen;
  473. // Construct logout or done+logout string
  474. if (m_fIDLE)
  475. {
  476. StrCpyN(sz, c_szDONE, ARRAYSIZE(sz));
  477. StrCpyN(sz + ARRAYSIZE(c_szDONE) - 1, cszLogoutCmd, (ARRAYSIZE(sz) - ARRAYSIZE(c_szDONE) + 1));
  478. iStrLen = ARRAYSIZE(c_szDONE) + ARRAYSIZE(cszLogoutCmd) - 2;
  479. }
  480. else
  481. {
  482. StrCpyN(sz, cszLogoutCmd, ARRAYSIZE(sz));
  483. iStrLen = ARRAYSIZE(cszLogoutCmd) - 1;
  484. }
  485. Assert(iStrLen == lstrlen(sz));
  486. hrLogoutResult = m_pSocket->SendBytes(sz, iStrLen, &iNumBytesSent);
  487. Assert(SUCCEEDED(hrLogoutResult));
  488. Assert(iNumBytesSent == iStrLen);
  489. if (m_pLogFile)
  490. m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT sent");
  491. }
  492. else {
  493. if (m_pLogFile)
  494. m_pLogFile->WriteLog(LOGFILE_TX, "Dropping connection, LOGOUT not sent");
  495. } // else
  496. exit:
  497. // Drop our connection, with status indication
  498. return CIxpBase::DropConnection();
  499. } // DropConnection
  500. //***************************************************************************
  501. // Function: ProcessServerGreeting
  502. //
  503. // Purpose:
  504. // This function is invoked when the receiver state machine is in
  505. // irsSVR_GREETING and a response line is received from the server. This
  506. // function takes a server greeting line (issued immediately when a
  507. // connection is established with the IMAP server) and parses it to
  508. // determine if: a) We are pre-authorized, and therefore do not need to
  509. // login, b) We have been refused the connection, or c) We must login.
  510. //
  511. // Arguments:
  512. // char *pszResponseLine [in] - the server greeting issued upon connection.
  513. // DWORD dwNumBytesReceived [in] - length of pszResponseLine string.
  514. //***************************************************************************
  515. void CImap4Agent::ProcessServerGreeting(char *pszResponseLine,
  516. DWORD dwNumBytesReceived)
  517. {
  518. HRESULT hrResult;
  519. IMAP_RESPONSE_ID irResult;
  520. char szFailureText[MAX_RESOURCESTRING];
  521. BOOL bUseLastResponse;
  522. Assert(m_lRefCount > 0);
  523. Assert(NULL != pszResponseLine);
  524. // Initialize variables
  525. szFailureText[0] = '\0';
  526. hrResult = E_FAIL;
  527. bUseLastResponse = FALSE;
  528. // Whatever happens next, we no longer expect server greeting - change state
  529. m_irsState = irsIDLE;
  530. // We have some kind of server response, so leave the busy state
  531. AssertSz(m_fBusy, "Check your logic: we should be busy until we get svr greeting!");
  532. LeaveBusy();
  533. // Server response is either OK, BYE or PREAUTH - find out which
  534. CheckForCompleteResponse(pszResponseLine, dwNumBytesReceived, &irResult);
  535. // Even if above fn fails, irResult should be valid (eg, irNONE)
  536. switch (irResult) {
  537. case irPREAUTH_RESPONSE:
  538. // We were pre-authorized by the server! Login is complete.
  539. // Send capability command
  540. Assert(ssAuthenticated == m_ssServerState);
  541. hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND,
  542. ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
  543. break;
  544. case irBYE_RESPONSE:
  545. // Server blew us off (ie, issued BYE)! Login failed.
  546. Assert(ssNotConnected == m_ssServerState);
  547. hrResult = IXP_E_IMAP_CONNECTION_REFUSED;
  548. LoadString(g_hLocRes, idsSvrRefusesConnection, szFailureText,
  549. ARRAYSIZE(szFailureText));
  550. bUseLastResponse = TRUE;
  551. break;
  552. case irOK_RESPONSE: {
  553. // Server response was "OK". We need to log in.
  554. Assert(ssConnecting == m_ssServerState);
  555. m_ssServerState = ssNonAuthenticated;
  556. // Send capability command - on its completion, we'll authenticate
  557. hrResult = NoArgCommand("CAPABILITY", icCAPABILITY_COMMAND,
  558. ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
  559. break;
  560. } // case hrIMAP_S_OK_RESPONSE
  561. default:
  562. // Has server gone absolutely LOOPY?
  563. AssertSz(FALSE, "What kind of server greeting is this?");
  564. hrResult = E_FAIL;
  565. LoadString(g_hLocRes, idsUnknownIMAPGreeting, szFailureText,
  566. ARRAYSIZE(szFailureText));
  567. bUseLastResponse = TRUE;
  568. break;
  569. } // switch(hrResult)
  570. if (FAILED(hrResult)) {
  571. if ('\0' == szFailureText[0]) {
  572. LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
  573. ARRAYSIZE(szFailureText));
  574. }
  575. // Terminate login procedure and notify user
  576. OnIMAPError(hrResult, szFailureText, bUseLastResponse);
  577. DropConnection();
  578. }
  579. } // ProcessServerGreeting
  580. //***************************************************************************
  581. // Function: LoginUser
  582. //
  583. // Purpose:
  584. // This function is responsible for kickstarting the login process.
  585. //
  586. // Returns:
  587. // Nothing, because any errors are reported via CmdCompletionNotification
  588. // callback to the user. Any errors encountered in this function will be
  589. // encountered during command transmittal, and so there's nothing further we
  590. // can do... may as well end the login process here.
  591. //***************************************************************************
  592. void CImap4Agent::LoginUser(void)
  593. {
  594. HRESULT hrResult;
  595. Assert(m_lRefCount > 0);
  596. Assert(ssNotConnected != m_ssServerState);
  597. AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
  598. // Put us in Authentication mode
  599. OnStatus(IXP_AUTHORIZING);
  600. // Check first if we're already authenticated (eg, by PREAUTH greeting)
  601. if (ssAuthenticated <= m_ssServerState) {
  602. // We were preauthed. Notify user that login is complete
  603. OnStatus(IXP_AUTHORIZED);
  604. return;
  605. }
  606. // Use the old "Login" trick (cleartext passwords and all)
  607. hrResult = TwoArgCommand("LOGIN", m_rServer.szUserName, m_rServer.szPassword,
  608. icLOGIN_COMMAND, ssNonAuthenticated, 0, 0, DEFAULT_CBHANDLER);
  609. if (FAILED(hrResult)) {
  610. char szFailureText[MAX_RESOURCESTRING];
  611. // Could not send cmd: terminate login procedure and notify user
  612. LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
  613. ARRAYSIZE(szFailureText));
  614. OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
  615. DropConnection();
  616. }
  617. } // LoginUser
  618. //***************************************************************************
  619. // Function: AuthenticateUser
  620. //
  621. // Purpose:
  622. // This function handles our behaviour during non-cleartext (SSPI)
  623. // authentication. It is very heavily based on CPOP3Transport::ResponseAUTH.
  624. // Note that due to server interpretation problems during testing, I decided
  625. // that BAD and NO responses will be treated as the same thing for purposes
  626. // of authentication.
  627. //
  628. // Arguments:
  629. // AUTH_EVENT aeEvent [in] - the authentication event currently occuring.
  630. // This can be something like aeCONTINUE, for instance.
  631. // LPSTR pszServerData [in] - any data from the server associated with the
  632. // current authentication event. Set to NULL if no data is applicable.
  633. // DWORD dwSizeOfData [in] - the size of the buffer pointed to by
  634. // pszServerData.
  635. //***************************************************************************
  636. void CImap4Agent::AuthenticateUser(AUTH_EVENT aeEvent, LPSTR pszServerData,
  637. DWORD dwSizeOfData)
  638. {
  639. HRESULT hrResult;
  640. UINT uiFailureTextID;
  641. BOOL fUseLastResponse;
  642. // Initialize variables
  643. hrResult = S_OK;
  644. uiFailureTextID = 0;
  645. fUseLastResponse = FALSE;
  646. // Suspend the watchdog for this entire function
  647. LeaveBusy();
  648. // Handle the events for which the current state is unimportant
  649. if (aeBAD_OR_NO_RESPONSE == aeEvent && asUNINITIALIZED < m_asAuthStatus.asCurrentState) {
  650. BOOL fTryNextAuthPkg;
  651. // Figure out whether we should try the next auth pkg, or re-try current
  652. if (asWAITFOR_CHALLENGE == m_asAuthStatus.asCurrentState ||
  653. asWAITFOR_AUTHENTICATION == m_asAuthStatus.asCurrentState)
  654. fTryNextAuthPkg = tamCURRENT_AUTH_METHOD;
  655. else
  656. fTryNextAuthPkg = tamNEXT_AUTH_METHOD;
  657. // Send the AUTHENTICATE command
  658. hrResult = TryAuthMethod(fTryNextAuthPkg, &uiFailureTextID);
  659. if (FAILED(hrResult))
  660. // No more auth methods to try: disconnect and end session
  661. fUseLastResponse = TRUE;
  662. else {
  663. // OK, wait for server response
  664. m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE;
  665. if (tamCURRENT_AUTH_METHOD == fTryNextAuthPkg)
  666. m_asAuthStatus.fPromptForCredentials = TRUE;
  667. }
  668. goto exit;
  669. }
  670. else if (aeABORT_AUTHENTICATION == aeEvent) {
  671. // We received an unknown tagged response from the server: bail
  672. hrResult = E_FAIL;
  673. uiFailureTextID = idsIMAPAbortAuth;
  674. fUseLastResponse = TRUE;
  675. goto exit;
  676. }
  677. // Now, process auth events based on our current state
  678. switch (m_asAuthStatus.asCurrentState) {
  679. case asUNINITIALIZED: {
  680. BOOL fResult;
  681. // Check conditions
  682. if (aeStartAuthentication != aeEvent) {
  683. AssertSz(FALSE, "You can only start authentication in this state");
  684. break;
  685. }
  686. Assert(NULL == pszServerData && 0 == dwSizeOfData);
  687. // Put us in Authentication mode
  688. OnStatus(IXP_AUTHORIZING);
  689. // Check first if we're already authenticated (eg, by PREAUTH greeting)
  690. if (ssAuthenticated <= m_ssServerState) {
  691. // We were preauthed. Notify user that login is complete
  692. OnStatus(IXP_AUTHORIZED);
  693. break;
  694. }
  695. // Initialize SSPI
  696. fResult = FIsSicilyInstalled();
  697. if (FALSE == fResult) {
  698. hrResult = E_FAIL;
  699. uiFailureTextID = idsIMAPSicilyInitFail;
  700. break;
  701. }
  702. hrResult = SSPIGetPackages(&m_asAuthStatus.pPackages,
  703. &m_asAuthStatus.cPackages);
  704. if (FAILED(hrResult)) {
  705. uiFailureTextID = idsIMAPSicilyPkgFailure;
  706. break;
  707. }
  708. // Send AUTHENTICATE command
  709. Assert(0 == m_asAuthStatus.iCurrentAuthToken);
  710. hrResult = TryAuthMethod(tamNEXT_AUTH_METHOD, &uiFailureTextID);
  711. if (FAILED(hrResult))
  712. break;
  713. m_asAuthStatus.asCurrentState = asWAITFOR_CONTINUE;
  714. } // case asUNINITIALIZED
  715. break; // case asUNINITIALIZED
  716. case asWAITFOR_CONTINUE: {
  717. SSPIBUFFER Negotiate;
  718. if (aeCONTINUE != aeEvent) {
  719. AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
  720. break;
  721. }
  722. // Server wants us to continue: send negotiation string
  723. hrResult = SSPILogon(&m_asAuthStatus.rSicInfo, m_asAuthStatus.fPromptForCredentials, SSPI_BASE64,
  724. m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1], &m_rServer, m_pCBHandler);
  725. if (FAILED(hrResult)) {
  726. // Suppress error reportage - user may have hit cancel
  727. hrResult = CancelAuthentication();
  728. break;
  729. }
  730. if (m_asAuthStatus.fPromptForCredentials) {
  731. m_asAuthStatus.fPromptForCredentials = FALSE; // Don't prompt again
  732. }
  733. hrResult = SSPIGetNegotiate(&m_asAuthStatus.rSicInfo, &Negotiate);
  734. if (FAILED(hrResult)) {
  735. // Suppress error reportage - user may have hit cancel
  736. // Or the command was killed (with the connection)
  737. // Only cancel if we still have a pending command...
  738. if(m_piciCmdInSending)
  739. hrResult = CancelAuthentication();
  740. break;
  741. }
  742. // Append CRLF to negotiation string
  743. Negotiate.szBuffer[Negotiate.cbBuffer - 1] = '\r';
  744. Negotiate.szBuffer[Negotiate.cbBuffer] = '\n';
  745. Negotiate.szBuffer[Negotiate.cbBuffer + 1] = '\0';
  746. Negotiate.cbBuffer += 2;
  747. Assert(Negotiate.cbBuffer <= sizeof(Negotiate.szBuffer));
  748. Assert(Negotiate.szBuffer[Negotiate.cbBuffer - 1] == '\0');
  749. hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE,
  750. Negotiate.szBuffer, Negotiate.cbBuffer - 1);
  751. if (FAILED(hrResult))
  752. break;
  753. m_asAuthStatus.asCurrentState = asWAITFOR_CHALLENGE;
  754. } // case asWAITFOR_CONTINUE
  755. break; // case asWAITFOR_CONTINUE
  756. case asWAITFOR_CHALLENGE: {
  757. SSPIBUFFER rChallenge, rResponse;
  758. int iChallengeLen;
  759. if (aeCONTINUE != aeEvent) {
  760. AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
  761. break;
  762. }
  763. // Server has given us a challenge: respond to challenge
  764. SSPISetBuffer(pszServerData, SSPI_STRING, 0, &rChallenge);
  765. hrResult = SSPIResponseFromChallenge(&m_asAuthStatus.rSicInfo, &rChallenge, &rResponse);
  766. if (FAILED(hrResult)) {
  767. // Suppress error reportage - user could have hit cancel
  768. hrResult = CancelAuthentication();
  769. break;
  770. }
  771. // Append CRLF to response string
  772. rResponse.szBuffer[rResponse.cbBuffer - 1] = '\r';
  773. rResponse.szBuffer[rResponse.cbBuffer] = '\n';
  774. rResponse.szBuffer[rResponse.cbBuffer + 1] = '\0';
  775. rResponse.cbBuffer += 2;
  776. Assert(rResponse.cbBuffer <= sizeof(rResponse.szBuffer));
  777. Assert(rResponse.szBuffer[rResponse.cbBuffer - 1] == '\0');
  778. hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE,
  779. rResponse.szBuffer, rResponse.cbBuffer - 1);
  780. if (FAILED(hrResult))
  781. break;
  782. if (FALSE == rResponse.fContinue)
  783. m_asAuthStatus.asCurrentState = asWAITFOR_AUTHENTICATION;
  784. } // case asWAITFOR_CHALLENGE
  785. break; // case asWAITFOR_CHALLENGE
  786. case asWAITFOR_AUTHENTICATION:
  787. // If OK response, do nothing
  788. if (aeOK_RESPONSE != aeEvent) {
  789. AssertSz(FALSE, "What am I supposed to do with this auth-event in this state?");
  790. break;
  791. }
  792. break; // case asWAITFOR_AUTHENTICATION
  793. case asCANCEL_AUTHENTICATION:
  794. AssertSz(aeBAD_OR_NO_RESPONSE == aeEvent, "I cancelled an authentication and didn't get BAD");
  795. break; // case asCANCEL_AUTHENTICATION
  796. default:
  797. AssertSz(FALSE, "Invalid or unhandled state?");
  798. break; // Default case
  799. } // switch (aeEvent)
  800. exit:
  801. if (FAILED(hrResult)) {
  802. char szFailureText[MAX_RESOURCESTRING];
  803. char szFailureFmt[MAX_RESOURCESTRING/4];
  804. char szGeneral[MAX_RESOURCESTRING/4]; // Ack, how big could the word, "General" be?
  805. LPSTR p, pszAuthPkg;
  806. LoadString(g_hLocRes, idsIMAPAuthFailedFmt, szFailureFmt, ARRAYSIZE(szFailureFmt));
  807. if (0 == m_asAuthStatus.iCurrentAuthToken) {
  808. LoadString(g_hLocRes, idsGeneral, szGeneral, ARRAYSIZE(szGeneral));
  809. pszAuthPkg = szGeneral;
  810. }
  811. else
  812. pszAuthPkg = m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1];
  813. p = szFailureText;
  814. p += wnsprintf(szFailureText, ARRAYSIZE(szFailureText), szFailureFmt, pszAuthPkg);
  815. if (0 != uiFailureTextID)
  816. LoadString(g_hLocRes, uiFailureTextID, p,
  817. ARRAYSIZE(szFailureText) - (DWORD) (p - szFailureText));
  818. OnIMAPError(hrResult, szFailureText, fUseLastResponse);
  819. DropConnection();
  820. }
  821. // Reawaken the watchdog, if required
  822. else if (FALSE == m_fBusy &&
  823. (NULL != m_piciPendingList || NULL != m_piciCmdInSending)) {
  824. hrResult = HrEnterBusy();
  825. Assert(SUCCEEDED(hrResult));
  826. }
  827. } // AuthenticateUser
  828. //***************************************************************************
  829. // Function: TryAuthMethod
  830. //
  831. // Purpose:
  832. // This function sends out an AUTHENTICATE command to the server with the
  833. // appropriate authentication method. The caller can choose which method is
  834. // more appropriate: he can re-try the current authentication method, or
  835. // move on to the next authentication command which is supported by both
  836. // server and client.
  837. //
  838. // Arguments:
  839. // BOOL fNextAuthMethod [in] - TRUE if we should attempt to move on to
  840. // the next authentication package. FALSE if we should re-try the
  841. // current authentication package.
  842. // UINT *puiFailureTextID [out] - in case of failure, (eg, no more auth
  843. // methods to try), this function returns a string resource ID here which
  844. // describes the error.
  845. //
  846. // Returns:
  847. // HRESULT indicating success or failure. Expected failure codes include:
  848. // IXP_E_IMAP_AUTH_NOT_POSSIBLE - indicates server does not support
  849. // any auth packages which are recognized on this computer.
  850. // IXP_E_IMAP_OUT_OF_AUTH_METHODS - indicates that one or more auth
  851. // methods were attempted, and no more auth methods are left to try.
  852. //***************************************************************************
  853. HRESULT CImap4Agent::TryAuthMethod(BOOL fNextAuthMethod, UINT *puiFailureTextID)
  854. {
  855. BOOL fFoundMatch;
  856. HRESULT hrResult;
  857. char szBuffer[CMDLINE_BUFSIZE];
  858. CIMAPCmdInfo *piciCommand;
  859. int iStartingAuthToken;
  860. LPSTR p;
  861. Assert(m_lRefCount > 0);
  862. // Initialize variables
  863. hrResult = S_OK;
  864. piciCommand = NULL;
  865. // Only accept cmds if server is in proper state
  866. if (ssNonAuthenticated != m_ssServerState) {
  867. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  868. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  869. }
  870. // If we've already tried an auth pkg, free its info
  871. if (0 != m_asAuthStatus.iCurrentAuthToken)
  872. SSPIFreeContext(&m_asAuthStatus.rSicInfo);
  873. // Find the next auth token (returned by svr) that we support on this computer
  874. fFoundMatch = FALSE;
  875. iStartingAuthToken = m_asAuthStatus.iCurrentAuthToken;
  876. while (fFoundMatch == FALSE &&
  877. m_asAuthStatus.iCurrentAuthToken < m_asAuthStatus.iNumAuthTokens &&
  878. fNextAuthMethod) {
  879. ULONG ul = 0;
  880. // Current m_asAuthStatus.iCurrentAuthToken serves as idx to NEXT auth token
  881. // Compare current auth token with all installed packages
  882. for (ul = 0; ul < m_asAuthStatus.cPackages; ul++) {
  883. if (0 == lstrcmpi(m_asAuthStatus.pPackages[ul].pszName,
  884. m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken])) {
  885. fFoundMatch = TRUE;
  886. break;
  887. } // if
  888. } // for
  889. // Update this to indicate the current auth token ORDINAL (not idx)
  890. m_asAuthStatus.iCurrentAuthToken += 1;
  891. } // while
  892. if (FALSE == fFoundMatch && fNextAuthMethod) {
  893. // Could not find next authentication method match-up
  894. if (0 == iStartingAuthToken) {
  895. *puiFailureTextID = idsIMAPAuthNotPossible;
  896. return IXP_E_IMAP_AUTH_NOT_POSSIBLE;
  897. }
  898. else {
  899. *puiFailureTextID = idsIMAPOutOfAuthMethods;
  900. return IXP_E_IMAP_OUT_OF_AUTH_METHODS;
  901. }
  902. }
  903. // OK, m_asAuthStatus.iCurrentAuthToken should now point to correct match
  904. piciCommand = new CIMAPCmdInfo(this, icAUTHENTICATE_COMMAND, ssNonAuthenticated,
  905. 0, 0, NULL);
  906. if (NULL == piciCommand) {
  907. *puiFailureTextID = idsMemory;
  908. return E_OUTOFMEMORY;
  909. }
  910. // Construct command line
  911. p = szBuffer;
  912. p += wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s %.300s\r\n", piciCommand->szTag, "AUTHENTICATE",
  913. m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iCurrentAuthToken-1]);
  914. // Send command
  915. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, (DWORD) (p - szBuffer));
  916. if (FAILED(hrResult))
  917. goto SendError;
  918. // Insert a pause, so we can perform challenge/response
  919. hrResult = SendPause(piciCommand);
  920. if (FAILED(hrResult))
  921. goto SendError;
  922. // Transmit command and register with IMAP response parser
  923. hrResult = SubmitIMAPCommand(piciCommand);
  924. SendError:
  925. if (FAILED(hrResult))
  926. delete piciCommand;
  927. return hrResult;
  928. } // TryAuthMethod
  929. //***************************************************************************
  930. // Function: CancelAuthentication
  931. //
  932. // Purpose:
  933. // This function cancels the authentication currently in progress,
  934. // typically due to a failure result from a Sicily function. It sends a "*"
  935. // to the server and puts us into cancellation mode.
  936. //
  937. // Returns:
  938. // HRESULT indicating success or failure.
  939. //***************************************************************************
  940. HRESULT CImap4Agent::CancelAuthentication(void)
  941. {
  942. HRESULT hrResult;
  943. hrResult = SendCmdLine(m_piciCmdInSending, sclINSERT_BEFORE_PAUSE, "*\r\n", 3);
  944. m_asAuthStatus.asCurrentState = asCANCEL_AUTHENTICATION;
  945. return hrResult;
  946. } // CancelAuthentication
  947. //***************************************************************************
  948. // Function: OnCommandCompletion
  949. //
  950. // Purpose:
  951. // This function is called whenever we have received a tagged response line
  952. // terminating the current command in progress, whether or not the command
  953. // result was successful or not. This function notifies the user of the
  954. // command's results, and handles other tasks such as updating our internal
  955. // mirror of the server state and calling notification functions.
  956. //
  957. // Arguments:
  958. // LPSTR szTag [in] - the tag found in the tagged response line. This will
  959. // be used to compare with a list of commands in progress when we allow
  960. // multiple simultaneous commands, but is not currently used.
  961. // HRESULT hrCompletionResult [in] - the HRESULT returned by the IMAP line
  962. // parsing functions, eg, S_OK or IXP_E_IMAP_SVR_SYNTAXERR.
  963. // IMAP_RESPONSE_ID irCompletionResponse [in] - identifies the status response
  964. // of the tagged response line (OK/NO/BAD).
  965. //***************************************************************************
  966. void CImap4Agent::OnCommandCompletion(LPSTR szTag, HRESULT hrCompletionResult,
  967. IMAP_RESPONSE_ID irCompletionResponse)
  968. {
  969. CIMAPCmdInfo *piciCompletedCmd;
  970. boolean bSuppressCompletionNotification;
  971. Assert(m_lRefCount > 0);
  972. Assert(NULL != szTag);
  973. Assert(NULL != m_piciPendingList || NULL != m_piciCmdInSending);
  974. bSuppressCompletionNotification = FALSE;
  975. // ** STEP ONE: Identify the corresponding command for given tagged response
  976. // Search the pending-command chain for the given tag
  977. piciCompletedCmd = RemovePendingCommand(szTag);
  978. if (NULL == piciCompletedCmd) {
  979. BOOL fLeaveBusy = FALSE;
  980. // Couldn't find in pending list, check the command in sending
  981. EnterCriticalSection(&m_csSendQueue);
  982. if (NULL != m_piciCmdInSending &&
  983. 0 == lstrcmp(szTag, m_piciCmdInSending->szTag)) {
  984. piciCompletedCmd = DequeueCommand();
  985. fLeaveBusy = TRUE;
  986. }
  987. else {
  988. AssertSz(FALSE, "Could not find cmd corresponding to tagged response!");
  989. }
  990. LeaveCriticalSection(&m_csSendQueue);
  991. // Now we're out of &m_csSendQueue, call LeaveBusy (needs m_cs). Avoids deadlock.
  992. if (fLeaveBusy)
  993. LeaveBusy(); // This needs CIxpBase::m_cs, so having &m_csSendQueue may deadlock
  994. }
  995. // Did we find a command which matches the given tag?
  996. if (NULL == piciCompletedCmd)
  997. return; // $REVIEW: Should probably return an error to user
  998. // $REVIEW: I don't think I need to bother to pump the send queue
  999. // ** STEP TWO: Perform end-of-command actions
  1000. // Translate hrCompletionResult depending on response received
  1001. switch (irCompletionResponse) {
  1002. case irOK_RESPONSE:
  1003. Assert(S_OK == hrCompletionResult);
  1004. break;
  1005. case irNO_RESPONSE:
  1006. Assert(S_OK == hrCompletionResult);
  1007. hrCompletionResult = IXP_E_IMAP_TAGGED_NO_RESPONSE;
  1008. break;
  1009. case irBAD_RESPONSE:
  1010. Assert(S_OK == hrCompletionResult);
  1011. hrCompletionResult = IXP_E_IMAP_BAD_RESPONSE;
  1012. break;
  1013. default:
  1014. // If none of the above, hrResult had better be failure
  1015. Assert(FAILED(hrCompletionResult));
  1016. break;
  1017. }
  1018. // Perform any actions which follow the successful (or unsuccessful)
  1019. // completion of an IMAP command
  1020. switch (piciCompletedCmd->icCommandID) {
  1021. case icAUTHENTICATE_COMMAND: {
  1022. AUTH_EVENT aeEvent;
  1023. // We always suppress completion notification for this command,
  1024. // because it is sent by internal code (not by the user)
  1025. bSuppressCompletionNotification = TRUE;
  1026. if (irOK_RESPONSE == irCompletionResponse)
  1027. aeEvent = aeOK_RESPONSE;
  1028. else if (irNO_RESPONSE == irCompletionResponse ||
  1029. irBAD_RESPONSE == irCompletionResponse)
  1030. aeEvent = aeBAD_OR_NO_RESPONSE;
  1031. else
  1032. aeEvent = aeABORT_AUTHENTICATION;
  1033. AuthenticateUser(aeEvent, NULL, 0);
  1034. if (SUCCEEDED(hrCompletionResult)) {
  1035. m_ssServerState = ssAuthenticated;
  1036. AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
  1037. OnStatus(IXP_AUTHORIZED);
  1038. }
  1039. // Make sure we were paused
  1040. Assert(iltPAUSE == piciCompletedCmd->pilqCmdLineQueue->
  1041. pilfFirstFragment->iltFragmentType);
  1042. } // case icAUTHENTICATE_COMMAND
  1043. break; // case icAUTHENTICATE_COMMAND
  1044. case icLOGIN_COMMAND:
  1045. // We always suppress completion notification for this command,
  1046. // because it is sent by internal code (not by the user)
  1047. bSuppressCompletionNotification = TRUE;
  1048. if (SUCCEEDED(hrCompletionResult)) {
  1049. m_ssServerState = ssAuthenticated;
  1050. AssertSz(FALSE == m_fBusy, "We should not be expecting any server responses here!");
  1051. OnStatus(IXP_AUTHORIZED);
  1052. }
  1053. else {
  1054. char szFailureText[MAX_RESOURCESTRING];
  1055. Assert(ssAuthenticated > m_ssServerState);
  1056. LoadString(g_hLocRes, idsFailedLogin, szFailureText,
  1057. ARRAYSIZE(szFailureText));
  1058. OnIMAPError(IXP_E_IMAP_LOGINFAILURE, szFailureText, USE_LAST_RESPONSE);
  1059. ReLoginUser(); // Re-attempt login
  1060. } // else
  1061. break; // case icLOGIN_COMMAND
  1062. case icCAPABILITY_COMMAND:
  1063. // We always suppress completion notification for this command
  1064. // because it is sent by internal code (not by the user)
  1065. bSuppressCompletionNotification = TRUE;
  1066. if (SUCCEEDED(hrCompletionResult)) {
  1067. AssertSz(m_fConnectAuth, "Now just HOW does IIMAPTransport user do auth?");
  1068. if (m_rServer.fTrySicily)
  1069. AuthenticateUser(aeStartAuthentication, NULL, 0);
  1070. else
  1071. LoginUser();
  1072. }
  1073. else {
  1074. char szFailureText[MAX_RESOURCESTRING];
  1075. // Stop login process and report error to caller
  1076. LoadString(g_hLocRes, idsIMAPFailedCapability, szFailureText,
  1077. ARRAYSIZE(szFailureText));
  1078. OnIMAPError(hrCompletionResult, szFailureText, USE_LAST_RESPONSE);
  1079. DropConnection();
  1080. }
  1081. break; // case icCAPABILITY_COMMAND
  1082. case icSELECT_COMMAND:
  1083. case icEXAMINE_COMMAND:
  1084. if (SUCCEEDED(hrCompletionResult))
  1085. m_ssServerState = ssSelected;
  1086. else
  1087. m_ssServerState = ssAuthenticated;
  1088. break; // case icSELECT_COMMAND and icEXAMINE_COMMAND
  1089. case icCLOSE_COMMAND:
  1090. // $REVIEW: Should tagged NO response also go to ssAuthenticated?
  1091. if (SUCCEEDED(hrCompletionResult)) {
  1092. m_ssServerState = ssAuthenticated;
  1093. ResetMsgSeqNumToUID();
  1094. }
  1095. break; // case icCLOSE_COMMAND
  1096. case icLOGOUT_COMMAND:
  1097. // We always suppress completion notification for this command
  1098. bSuppressCompletionNotification = TRUE; // User can't send logout: it's sent internally
  1099. // Drop the connection (without status indication) regardless of
  1100. // whether LOGOUT succeeded or failed
  1101. Assert(SUCCEEDED(hrCompletionResult)); // Debug-only detection of hanky-panky
  1102. m_pSocket->Close();
  1103. ResetMsgSeqNumToUID(); // Just in case, SHOULD be handled by OnDisconnected,FreeAllData
  1104. break; // case icLOGOUT_COMMAND;
  1105. case icIDLE_COMMAND:
  1106. bSuppressCompletionNotification = TRUE; // User can't send IDLE: it's sent internally
  1107. m_fIDLE = FALSE; // We are now out of IDLE mode
  1108. break; // case icIDLE_COMMAND
  1109. case icAPPEND_COMMAND:
  1110. m_dwAppendStreamUploaded = 0;
  1111. m_dwAppendStreamTotal = 0;
  1112. break; // case icAPPEND_COMMAND
  1113. } // switch (piciCompletedCmd->icCommandID)
  1114. // ** STEP THREE: Perform notifications.
  1115. // Notify the user that this command has completed, unless we're told to
  1116. // suppress it (usually done to treat the multi-step login process as
  1117. // one operation).
  1118. if (FALSE == bSuppressCompletionNotification) {
  1119. IMAP_RESPONSE irIMAPResponse;
  1120. irIMAPResponse.wParam = piciCompletedCmd->wParam;
  1121. irIMAPResponse.lParam = piciCompletedCmd->lParam;
  1122. irIMAPResponse.hrResult = hrCompletionResult;
  1123. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  1124. irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
  1125. OnIMAPResponse(piciCompletedCmd->pCBHandler, &irIMAPResponse);
  1126. }
  1127. // Delete CIMAPCmdInfo object
  1128. // Note that deleting a CIMAPCmdInfo object automatically flushes its send queue
  1129. delete piciCompletedCmd;
  1130. // Finally, pump the send queue, if another cmd is available
  1131. if (NULL != m_piciSendQueue)
  1132. ProcessSendQueue(iseSEND_COMMAND);
  1133. else if (NULL == m_piciPendingList &&
  1134. m_ssServerState >= ssAuthenticated && irsIDLE == m_irsState)
  1135. // Both m_piciSendQueue and m_piciPendingList are empty: send IDLE cmd
  1136. EnterIdleMode();
  1137. } // OnCommandCompletion
  1138. //***************************************************************************
  1139. // Function: CheckForCompleteResponse
  1140. //
  1141. // Purpose:
  1142. // Given a response line (which isn't part of a literal), this function
  1143. // checks the end of the line to see if a literal is coming. If so, then we
  1144. // prepare the receiver FSM for it. Otherwise, this constitutes the end
  1145. // of an IMAP response, so we may parse as required.
  1146. //
  1147. // Arguments:
  1148. // LPSTR pszResponseLine [in] - this points to the response line sent to
  1149. // us by the IMAP server.
  1150. // DWORD dwNumBytesRead [in] - the length of pszResponseLine.
  1151. // IMAP_RESPONSE_ID *pirParseResult [out] - if the function determines that
  1152. // we can parse the response, the parse result is stored here (eg,
  1153. // irOK_RESPONSE). Otherwise, irNONE is written to the pointed location.
  1154. //***************************************************************************
  1155. void CImap4Agent::CheckForCompleteResponse(LPSTR pszResponseLine,
  1156. DWORD dwNumBytesRead,
  1157. IMAP_RESPONSE_ID *pirParseResult)
  1158. {
  1159. HRESULT hrResult;
  1160. boolean bTagged;
  1161. IMAP_LINE_FRAGMENT *pilfLine;
  1162. LPSTR psz;
  1163. BOOL fLiteral = FALSE;
  1164. Assert(m_lRefCount > 0);
  1165. Assert(NULL != pszResponseLine);
  1166. Assert(NULL == m_pilfLiteralInProgress);
  1167. Assert(0 == m_dwLiteralInProgressBytesLeft);
  1168. Assert(NULL != pirParseResult);
  1169. Assert(irsIDLE == m_irsState || irsSVR_GREETING == m_irsState);
  1170. *pirParseResult = irNONE;
  1171. // This is a LINE (not literal), so we're OK to nuke CRLF at end
  1172. Assert(dwNumBytesRead >= 2); // All lines must have at least CRLF
  1173. *(pszResponseLine + dwNumBytesRead - 2) = '\0';
  1174. // Create line fragment
  1175. pilfLine = new IMAP_LINE_FRAGMENT;
  1176. pilfLine->iltFragmentType = iltLINE;
  1177. pilfLine->ilsLiteralStoreType = ilsSTRING;
  1178. pilfLine->dwLengthOfFragment = dwNumBytesRead - 2; // Subtract nuked CRLF
  1179. pilfLine->data.pszSource = pszResponseLine;
  1180. pilfLine->pilfNextFragment = NULL;
  1181. pilfLine->pilfPrevFragment = NULL;
  1182. EnqueueFragment(pilfLine, &m_ilqRecvQueue);
  1183. // Now check last char in line (exclude CRLF) to see if a literal is forthcoming
  1184. psz = pszResponseLine + dwNumBytesRead -
  1185. min(dwNumBytesRead, 3); // Points to '}' if literal is coming
  1186. if ('}' == *psz) {
  1187. LPSTR pszLiteral;
  1188. // IE5 bug #30672: It is valid for a line to end in "}" and not be a literal.
  1189. // We must confirm that there are digits and an opening brace "{" to detect a literal
  1190. pszLiteral = psz;
  1191. while (TRUE) {
  1192. pszLiteral -= 1;
  1193. if (pszLiteral < pszResponseLine)
  1194. break;
  1195. if ('{' == *pszLiteral) {
  1196. fLiteral = TRUE;
  1197. psz = pszLiteral;
  1198. break;
  1199. }
  1200. else if (*pszLiteral < '0' || *pszLiteral > '9')
  1201. // Assert(FALSE) (placeholder)
  1202. // *** Consider using isdigit or IsDigit? ***
  1203. break; // This is not a literal
  1204. }
  1205. }
  1206. if (FALSE == fLiteral) {
  1207. char szTag[NUM_TAG_CHARS+1];
  1208. // No literal is forthcoming. This is a complete line, so let's parse
  1209. // Get ptr to first fragment, then nuke receive queue so we can
  1210. // continue to receive response lines while parsing this one
  1211. pilfLine = m_ilqRecvQueue.pilfFirstFragment;
  1212. m_ilqRecvQueue = ImapLinefragQueue_INIT;
  1213. // Parse line. Note that parsing code is responsible for advancing
  1214. // pilfLine so that it points to the current fragment being parsed.
  1215. // Fragments which have been fully processed should be freed by
  1216. // the parsing code (except for the last fragment)
  1217. hrResult = ParseSvrResponseLine(&pilfLine, &bTagged, szTag, pirParseResult);
  1218. // Flush rest of recv queue, regardless of parse result
  1219. while (NULL != pilfLine) {
  1220. IMAP_LINE_FRAGMENT *pilfTemp;
  1221. pilfTemp = pilfLine->pilfNextFragment;
  1222. FreeFragment(&pilfLine);
  1223. pilfLine = pilfTemp;
  1224. }
  1225. if (bTagged)
  1226. OnCommandCompletion(szTag, hrResult, *pirParseResult);
  1227. else if (FAILED(hrResult)) {
  1228. IMAP_RESPONSE irIMAPResponse;
  1229. IIMAPCallback *pCBHandler;
  1230. // Report untagged response failures via ErrorNotification callback
  1231. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  1232. &pCBHandler, *pirParseResult);
  1233. irIMAPResponse.hrResult = hrResult;
  1234. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  1235. irIMAPResponse.irtResponseType = irtERROR_NOTIFICATION;
  1236. // Log it
  1237. if (m_pLogFile) {
  1238. char szErrorTxt[64];
  1239. wnsprintf(szErrorTxt, ARRAYSIZE(szErrorTxt), "PARSE ERROR: hr=%lu", hrResult);
  1240. m_pLogFile->WriteLog(LOGFILE_DB, szErrorTxt);
  1241. }
  1242. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  1243. }
  1244. }
  1245. else {
  1246. DWORD dwLengthOfLiteral, dwMsgSeqNum;
  1247. LPSTR pszBodyTag;
  1248. if ('{' != *psz) {
  1249. Assert(FALSE); // What is this?
  1250. return; // Nothing we can do, we obviously can't get size of literal
  1251. }
  1252. else
  1253. dwLengthOfLiteral = StrToUint(psz + 1);
  1254. // Prepare either for FETCH body, or a regular literal
  1255. if (isFetchResponse(&m_ilqRecvQueue, &dwMsgSeqNum) &&
  1256. isFetchBodyLiteral(pilfLine, psz, &pszBodyTag)) {
  1257. // Prepare (tombstone) literal first, because it puts us in literal mode
  1258. hrResult = PrepareForLiteral(0);
  1259. // This will override literal mode, putting us in fetch body part mode
  1260. // Ignore PrepareForLiteral failure: if we don't, we interpret EACH LINE of
  1261. // the fetch body as an IMAP response line.
  1262. PrepareForFetchBody(dwMsgSeqNum, dwLengthOfLiteral, pszBodyTag);
  1263. }
  1264. else
  1265. hrResult = PrepareForLiteral(dwLengthOfLiteral);
  1266. Assert(SUCCEEDED(hrResult)); // Not much else we can do
  1267. } // else: handles case where a literal is coming after this line
  1268. } // CheckForCompleteResponse
  1269. //***************************************************************************
  1270. // Function: PrepareForLiteral
  1271. //
  1272. // Purpose:
  1273. // This function prepares the receiver code to receive a literal from the
  1274. // IMAP server.
  1275. //
  1276. // Arguments:
  1277. // DWORD dwSizeOfLiteral [in] - the size of the incoming literal as
  1278. // reported by the IMAP server.
  1279. //
  1280. // Returns:
  1281. // HRESULT indicating success or failure.
  1282. //***************************************************************************
  1283. HRESULT CImap4Agent::PrepareForLiteral(DWORD dwSizeOfLiteral)
  1284. {
  1285. IMAP_LINE_FRAGMENT *pilfLiteral;
  1286. HRESULT hrResult;
  1287. // Initialize variables
  1288. hrResult = S_OK;
  1289. // Construct line fragment of type iltLITERAL
  1290. Assert(NULL == m_pilfLiteralInProgress);
  1291. pilfLiteral = new IMAP_LINE_FRAGMENT;
  1292. if (NULL == pilfLiteral)
  1293. return E_OUTOFMEMORY;
  1294. pilfLiteral->iltFragmentType = iltLITERAL;
  1295. pilfLiteral->dwLengthOfFragment = dwSizeOfLiteral;
  1296. pilfLiteral->pilfNextFragment = NULL;
  1297. pilfLiteral->pilfPrevFragment = NULL;
  1298. // Allocate string or stream to hold literal, depending on its size
  1299. if (pilfLiteral->dwLengthOfFragment > dwLITERAL_THRESHOLD) {
  1300. // Literal is big, so store it as stream (large literals often represent
  1301. // data which we return to the user as a stream, eg, message bodies)
  1302. pilfLiteral->ilsLiteralStoreType = ilsSTREAM;
  1303. hrResult = MimeOleCreateVirtualStream(&pilfLiteral->data.pstmSource);
  1304. }
  1305. else {
  1306. BOOL bResult;
  1307. // Literal is small. Store it as a string rather than a stream, since
  1308. // CImap4Agent functions probably expect it as a string, anyways.
  1309. pilfLiteral->ilsLiteralStoreType = ilsSTRING;
  1310. bResult = MemAlloc((void **) &pilfLiteral->data.pszSource,
  1311. pilfLiteral->dwLengthOfFragment + 1); // Room for null-term
  1312. if (FALSE == bResult)
  1313. hrResult = E_OUTOFMEMORY;
  1314. else {
  1315. hrResult = S_OK;
  1316. *(pilfLiteral->data.pszSource) = '\0'; // Null-terminate the string
  1317. }
  1318. }
  1319. if (FAILED(hrResult))
  1320. delete pilfLiteral; // Failure means no data.pstmSource or data.pszSource to dealloc
  1321. else {
  1322. // Set up receive FSM to receive the proper number of bytes for literal
  1323. m_pilfLiteralInProgress = pilfLiteral;
  1324. m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral;
  1325. m_irsState = irsLITERAL;
  1326. }
  1327. return hrResult;
  1328. } // PrepareForLiteral
  1329. //***************************************************************************
  1330. // Function: isFetchResponse
  1331. //
  1332. // Purpose:
  1333. // This function determines if the given IMAP line fragment queue holds
  1334. // a FETCH response. If so, its message sequence number may be returned to
  1335. // the caller.
  1336. //
  1337. // Arguments:
  1338. // IMAP_LINEFRAG_QUEUE *pilqCurrentResponse [in] - a line fragment queue
  1339. // which may or may not hold a FETCH response.
  1340. // LPDWORD pdwMsgSeqNum [out] - if pilqCurrentResponse points to a FETCH
  1341. // response, its message sequence number is returned here. This argument
  1342. // may be NULL if the user does not care.
  1343. //
  1344. // Returns:
  1345. // TRUE if pilqCurrentResponse held a FETCH response. Otherwise, FALSE.
  1346. //***************************************************************************
  1347. BOOL CImap4Agent::isFetchResponse(IMAP_LINEFRAG_QUEUE *pilqCurrentResponse,
  1348. LPDWORD pdwMsgSeqNum)
  1349. {
  1350. LPSTR pszMsgSeqNum;
  1351. Assert(NULL != pilqCurrentResponse);
  1352. Assert(NULL != pilqCurrentResponse->pilfFirstFragment);
  1353. Assert(iltLINE == pilqCurrentResponse->pilfFirstFragment->iltFragmentType);
  1354. if (NULL != pdwMsgSeqNum)
  1355. *pdwMsgSeqNum = 0; // At least it won't be random
  1356. pszMsgSeqNum = pilqCurrentResponse->pilfFirstFragment->data.pszSource;
  1357. // Advance pointer to the message sequence number
  1358. if ('*' != *pszMsgSeqNum)
  1359. return FALSE; // We only handle tagged responses
  1360. pszMsgSeqNum += 1;
  1361. if (cSPACE != *pszMsgSeqNum)
  1362. return FALSE;
  1363. pszMsgSeqNum += 1;
  1364. if (*pszMsgSeqNum >= '0' && *pszMsgSeqNum <= '9') {
  1365. LPSTR pszEndOfNumber;
  1366. int iResult;
  1367. pszEndOfNumber = StrChr(pszMsgSeqNum, cSPACE); // Find the end of the number
  1368. if (NULL == pszEndOfNumber)
  1369. return FALSE; // This ain't no FETCH response
  1370. iResult = StrCmpNI(pszEndOfNumber + 1, "FETCH ", 6);
  1371. if (0 == iResult) {
  1372. if (NULL != pdwMsgSeqNum)
  1373. *pdwMsgSeqNum = StrToUint(pszMsgSeqNum);
  1374. return TRUE;
  1375. }
  1376. }
  1377. // If we hit this point, it wasn't a FETCH response
  1378. return FALSE;
  1379. } // isFetchResponse
  1380. //***************************************************************************
  1381. // Function: isFetchBodyLiteral
  1382. //
  1383. // Purpose:
  1384. // This function is called when the caller knows he has a FETCH response,
  1385. // and when the FETCH response is about to send a literal. This function will
  1386. // determine whether the literal about to be sent contains a message body
  1387. // part (like RFC822), or whether the literal is something else (like an
  1388. // nstring sent as a literal inside a BODYSTRUCTURE).
  1389. //
  1390. // Arguments:
  1391. // IMAP_LINE_FRAGMENT *pilfCurrent [in] - a pointer to the current line
  1392. // fragment received from the server. It is used by this function to
  1393. // rewind past any literals we may have received in the "section" of
  1394. // the BODY "msg_att" (see RFC2060 formal syntax).
  1395. // LPSTR pszStartOfLiteralSize [in] - a pointer to the start of the '{'
  1396. // character which indicates that a literal is coming (eg, {123}
  1397. // indicates a literal of size 123 is coming, and pszStartOfLiteralSize
  1398. // would point to the '{' in this case).
  1399. // LPSTR *ppszBodyTag [out] - if the literal about to be sent contains a
  1400. // message body part, a dup of the tag (eg, "RFC822" or "BODY[2.2]") is
  1401. // returned to the caller here. It is the caller's responsibility to
  1402. // MemFree this tag. THIS TAG WILL NOT CONTAIN ANY SPACES. Thus even though
  1403. // the server may return "BODY[HEADER.FIELDS (foo bar)]", this function
  1404. // only returns "BODY[HEADER.FIELDS".
  1405. //
  1406. // Returns:
  1407. // TRUE if the literal about to be sent contains a message body part.
  1408. // FALSE otherwise.
  1409. //***************************************************************************
  1410. BOOL CImap4Agent::isFetchBodyLiteral(IMAP_LINE_FRAGMENT *pilfCurrent,
  1411. LPSTR pszStartOfLiteralSize,
  1412. LPSTR *ppszBodyTag)
  1413. {
  1414. LPSTR pszStartOfLine;
  1415. LPSTR pszStartOfFetchAtt;
  1416. LPSTR pszMostRecentSpace;
  1417. int iNumDelimiters;
  1418. BOOL fBodySection = FALSE;
  1419. Assert(NULL != pilfCurrent);
  1420. Assert(NULL != pszStartOfLiteralSize);
  1421. Assert(pszStartOfLiteralSize >= pilfCurrent->data.pszSource &&
  1422. pszStartOfLiteralSize < (pilfCurrent->data.pszSource + pilfCurrent->dwLengthOfFragment));
  1423. Assert(NULL != ppszBodyTag);
  1424. // Initialize variables
  1425. *ppszBodyTag = NULL;
  1426. Assert('{' == *pszStartOfLiteralSize);
  1427. // Get pointer to current msg_att: we only care about RFC822* or BODY[...]. ENVELOPE ({5} doesn't count
  1428. iNumDelimiters = 0;
  1429. pszStartOfLine = pilfCurrent->data.pszSource;
  1430. pszStartOfFetchAtt = pszStartOfLiteralSize;
  1431. pszMostRecentSpace = pszStartOfLiteralSize;
  1432. while (iNumDelimiters < 2) {
  1433. // Check if we have recoiled to the start of current string buffer
  1434. if (pszStartOfFetchAtt <= pszStartOfLine) {
  1435. // We need to recoil to previous string buffer. It is likely that a literal
  1436. // is in the way, and it is likely that this literal belongs to HEADER.FIELDS
  1437. // (but this can also happen inside an ENVELOPE)
  1438. // Skip literals and anything else that's not a line
  1439. do {
  1440. pilfCurrent = pilfCurrent->pilfPrevFragment;
  1441. } while (NULL != pilfCurrent && iltLINE != pilfCurrent->iltFragmentType);
  1442. if (NULL == pilfCurrent || 0 == pilfCurrent->dwLengthOfFragment) {
  1443. // This ain't no FETCH BODY, near as I can tell
  1444. Assert(iNumDelimiters < 2);
  1445. break;
  1446. }
  1447. else {
  1448. // Reset string pointers
  1449. Assert(iltLINE == pilfCurrent->iltFragmentType &&
  1450. ilsSTRING == pilfCurrent->ilsLiteralStoreType);
  1451. pszStartOfLine = pilfCurrent->data.pszSource;
  1452. // Note that pszStartOfFetchAtt will recoil past literal size decl ("{123}")
  1453. // That's OK because it won't contain any of the delimiters we're looking for
  1454. pszStartOfFetchAtt = pszStartOfLine + pilfCurrent->dwLengthOfFragment; // Points to null-term
  1455. pszMostRecentSpace = pszStartOfFetchAtt; // Points to null-term (that's OK)
  1456. }
  1457. }
  1458. // Set pszMostRecentSpace before pszStartOfFetchAtt decrement so pszMostRecentSpace
  1459. // isn't set to the space BEFORE the fetch body tag
  1460. if (cSPACE == *pszStartOfFetchAtt)
  1461. pszMostRecentSpace = pszStartOfFetchAtt;
  1462. pszStartOfFetchAtt -= 1;
  1463. // Check for nested brackets (should not be allowed)
  1464. Assert(']' != *pszStartOfFetchAtt || fBodySection == FALSE);
  1465. // Disable delimiter-counting if we're in the middle of RFC2060 formal syntax "section"
  1466. // because the HEADER.FIELDS (...) section contains spaces and parentheses
  1467. if (']' == *pszStartOfFetchAtt)
  1468. fBodySection = TRUE;
  1469. else if ('[' == *pszStartOfFetchAtt)
  1470. fBodySection = FALSE;
  1471. if (FALSE == fBodySection && (cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt))
  1472. iNumDelimiters += 1;
  1473. }
  1474. if (iNumDelimiters < 2)
  1475. return FALSE; // This isn't a body tag
  1476. Assert(2 == iNumDelimiters);
  1477. Assert(cSPACE == *pszStartOfFetchAtt || '(' == *pszStartOfFetchAtt);
  1478. pszStartOfFetchAtt += 1; // Make it point to the start of the tag
  1479. if (0 == StrCmpNI(pszStartOfFetchAtt, "RFC822", 6) ||
  1480. 0 == StrCmpNI(pszStartOfFetchAtt, "BODY[", 5)) {
  1481. int iSizeOfBodyTag;
  1482. BOOL fResult;
  1483. Assert(pszMostRecentSpace >= pszStartOfLine && (NULL == pilfCurrent ||
  1484. pszMostRecentSpace <= pszStartOfLine + pilfCurrent->dwLengthOfFragment));
  1485. Assert(pszStartOfFetchAtt >= pszStartOfLine && (NULL == pilfCurrent ||
  1486. pszStartOfFetchAtt <= pszStartOfLine + pilfCurrent->dwLengthOfFragment));
  1487. Assert(pszMostRecentSpace >= pszStartOfFetchAtt);
  1488. // Return a duplicate of the body tag, up until the first space +1 for null-term
  1489. iSizeOfBodyTag = (int) (pszMostRecentSpace - pszStartOfFetchAtt + 1);
  1490. fResult = MemAlloc((void **)ppszBodyTag, iSizeOfBodyTag);
  1491. if (FALSE == fResult)
  1492. return FALSE;
  1493. CopyMemory(*ppszBodyTag, pszStartOfFetchAtt, iSizeOfBodyTag);
  1494. *(*ppszBodyTag + iSizeOfBodyTag - 1) = '\0'; // Null-terminate the body tag dup
  1495. return TRUE;
  1496. }
  1497. // If we reached this point, this is not a body tag
  1498. return FALSE;
  1499. } // isFetchBodyLiteral
  1500. //***************************************************************************
  1501. // Function: PrepareForFetchBody
  1502. //
  1503. // Purpose:
  1504. // This function prepares the receiver code to receive a literal which
  1505. // contains a message body part. This literal will always be part of a FETCH
  1506. // response from the IMAP server.
  1507. //
  1508. // Arguments:
  1509. // DWORD dwMsgSeqNum [in] - the message sequence number of the FETCH
  1510. // response currently being received from the IMAP server.
  1511. // DWORD dwSizeOfLiteral [in] - the size of the literal about to be received
  1512. // from the server.
  1513. // LPSTR pszBodyTag [in] - a pointer to a dup of the IMAP msg_att (eg,
  1514. // "RFC822" or "BODY[2.2]") which identifies the current literal. Look up
  1515. // msg_att in RFC2060's formal syntax section for details. This dup will
  1516. // be MemFree'ed when it is no longer needed.
  1517. //***************************************************************************
  1518. void CImap4Agent::PrepareForFetchBody(DWORD dwMsgSeqNum, DWORD dwSizeOfLiteral,
  1519. LPSTR pszBodyTag)
  1520. {
  1521. Assert(0 == m_dwLiteralInProgressBytesLeft);
  1522. m_fbpFetchBodyPartInProgress.dwMsgSeqNum = dwMsgSeqNum;
  1523. m_fbpFetchBodyPartInProgress.pszBodyTag = pszBodyTag;
  1524. m_fbpFetchBodyPartInProgress.dwTotalBytes = dwSizeOfLiteral;
  1525. m_fbpFetchBodyPartInProgress.dwSizeOfData = 0;
  1526. m_fbpFetchBodyPartInProgress.dwOffset = 0;
  1527. m_fbpFetchBodyPartInProgress.fDone = 0;
  1528. m_fbpFetchBodyPartInProgress.pszData = NULL;
  1529. // Leave the cookies alone, so they persist throughout FETCH response
  1530. m_dwLiteralInProgressBytesLeft = dwSizeOfLiteral;
  1531. m_irsState = irsFETCH_BODY;
  1532. } // PrepareForFetchBody
  1533. //***************************************************************************
  1534. // Function: AddBytesToLiteral
  1535. //
  1536. // Purpose:
  1537. // This function is called whenever we receive an AE_RECV from the IMAP
  1538. // server while the receiver FSM is in irsLITERAL mode. The caller is
  1539. // expected to call CASyncConn::ReadBytes and update the literal byte-count.
  1540. // This function just handles the buffer-work.
  1541. //
  1542. // Arguments:
  1543. // LPSTR pszResponseBuf [in] - the buffer of data returned via
  1544. // CASyncConn::ReadBytes.
  1545. // DWORD dwNumBytesRead [in] - the size of the buffer pointed to by
  1546. // CASyncConn::ReadBytes.
  1547. //***************************************************************************
  1548. void CImap4Agent::AddBytesToLiteral(LPSTR pszResponseBuf, DWORD dwNumBytesRead)
  1549. {
  1550. Assert(m_lRefCount > 0);
  1551. Assert(NULL != pszResponseBuf);
  1552. if (NULL == m_pilfLiteralInProgress) {
  1553. AssertSz(FALSE, "I'm still in irsLITERAL state, but I'm not set up to recv literals!");
  1554. m_irsState = irsIDLE;
  1555. goto exit;
  1556. }
  1557. // Find out if this literal will be stored as a string or stream (this
  1558. // decision was made in CheckForCompleteResponse using size of literal).
  1559. Assert(iltLITERAL == m_pilfLiteralInProgress->iltFragmentType);
  1560. if (ilsSTREAM == m_pilfLiteralInProgress->ilsLiteralStoreType) {
  1561. HRESULT hrResult;
  1562. ULONG ulNumBytesWritten;
  1563. // Store literal as stream
  1564. hrResult = (m_pilfLiteralInProgress->data.pstmSource)->Write(pszResponseBuf,
  1565. dwNumBytesRead, &ulNumBytesWritten);
  1566. Assert(SUCCEEDED(hrResult) && ulNumBytesWritten == dwNumBytesRead);
  1567. }
  1568. else {
  1569. LPSTR pszLiteralStartPoint;
  1570. // Concatenate literal to literal in progress
  1571. // $REVIEW: Perf enhancement - CALCULATE insertion point
  1572. pszLiteralStartPoint = m_pilfLiteralInProgress->data.pszSource +
  1573. lstrlen(m_pilfLiteralInProgress->data.pszSource);
  1574. Assert(pszLiteralStartPoint + dwNumBytesRead <=
  1575. m_pilfLiteralInProgress->data.pszSource +
  1576. m_pilfLiteralInProgress->dwLengthOfFragment);
  1577. CopyMemory(pszLiteralStartPoint, pszResponseBuf, dwNumBytesRead);
  1578. *(pszLiteralStartPoint + dwNumBytesRead) = '\0'; // Null-terminate
  1579. }
  1580. // Check for end-of-literal
  1581. if (0 == m_dwLiteralInProgressBytesLeft) {
  1582. // We now have the complete literal! Queue it up and move on
  1583. EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue);
  1584. m_irsState = irsIDLE;
  1585. m_pilfLiteralInProgress = NULL;
  1586. }
  1587. exit:
  1588. SafeMemFree(pszResponseBuf);
  1589. } // AddBytesToLiteral
  1590. //***************************************************************************
  1591. // Function: DispatchFetchBodyPart
  1592. //
  1593. // Purpose:
  1594. // This function is called whenever receive a packet which is part of a
  1595. // message body part of a FETCH response. This packet is dispatched to the
  1596. // caller in this function via the OnResponse(irtFETCH_BODY) callback. If
  1597. // the message body part is finished, this function also restores the
  1598. // receiver code to receive lines so that the FETCH response may be completed.
  1599. //
  1600. // Arguments:
  1601. // LPSTR pszResponseBuf [in] - a pointer to the packet which is part of
  1602. // the message body part of the current FETCH response.
  1603. // DWORD dwNumBytesRead [in] - the size of the data pointed to by
  1604. // pszResponseBuf.
  1605. // BOOL fFreeBodyTagAtEnd [in] - TRUE if
  1606. // m_fbpFetchBodyPartInProgress.pszBodyTag points to a string dup, in
  1607. // which case it must be MemFree'ed when the message body part is
  1608. // finished. FALSE if the pszBodyTag member must not be MemFree'ed.
  1609. //***************************************************************************
  1610. void CImap4Agent::DispatchFetchBodyPart(LPSTR pszResponseBuf,
  1611. DWORD dwNumBytesRead,
  1612. BOOL fFreeBodyTagAtEnd)
  1613. {
  1614. IMAP_RESPONSE irIMAPResponse;
  1615. AssertSz(0 != m_fbpFetchBodyPartInProgress.dwMsgSeqNum,
  1616. "Are you sure you're set up to receive a Fetch Body Part?");
  1617. // Update the FETCH body part structure
  1618. m_fbpFetchBodyPartInProgress.dwSizeOfData = dwNumBytesRead;
  1619. m_fbpFetchBodyPartInProgress.pszData = pszResponseBuf;
  1620. m_fbpFetchBodyPartInProgress.fDone =
  1621. (m_fbpFetchBodyPartInProgress.dwOffset + dwNumBytesRead >=
  1622. m_fbpFetchBodyPartInProgress.dwTotalBytes);
  1623. // Send an IMAP response callback for this body part
  1624. irIMAPResponse.wParam = 0;
  1625. irIMAPResponse.lParam = 0;
  1626. irIMAPResponse.hrResult = S_OK;
  1627. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  1628. irIMAPResponse.irtResponseType = irtFETCH_BODY;
  1629. irIMAPResponse.irdResponseData.pFetchBodyPart = &m_fbpFetchBodyPartInProgress;
  1630. AssertSz(S_OK == irIMAPResponse.hrResult,
  1631. "Make sure fDone is TRUE if FAILED(hrResult))");
  1632. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  1633. // Update the next buffer's offset
  1634. m_fbpFetchBodyPartInProgress.dwOffset += dwNumBytesRead;
  1635. // Check for end of body part
  1636. if (m_fbpFetchBodyPartInProgress.dwOffset >=
  1637. m_fbpFetchBodyPartInProgress.dwTotalBytes) {
  1638. Assert(0 == m_dwLiteralInProgressBytesLeft);
  1639. Assert(TRUE == m_fbpFetchBodyPartInProgress.fDone);
  1640. Assert(m_fbpFetchBodyPartInProgress.dwOffset == m_fbpFetchBodyPartInProgress.dwTotalBytes);
  1641. if (fFreeBodyTagAtEnd)
  1642. MemFree(m_fbpFetchBodyPartInProgress.pszBodyTag);
  1643. // Enqueue the tombstone literal, if fetch body nstring was sent as literal
  1644. if (NULL != m_pilfLiteralInProgress) {
  1645. EnqueueFragment(m_pilfLiteralInProgress, &m_ilqRecvQueue);
  1646. m_pilfLiteralInProgress = NULL;
  1647. }
  1648. // Zero the fetch body part structure, but leave the cookies
  1649. PrepareForFetchBody(0, 0, NULL);
  1650. m_irsState = irsIDLE; // Overrides irsFETCH_BODY set by PrepareForFetchBody
  1651. }
  1652. else {
  1653. Assert(FALSE == m_fbpFetchBodyPartInProgress.fDone);
  1654. }
  1655. } // DispatchFetchBodyPart
  1656. //***************************************************************************
  1657. // Function: UploadStreamProgress
  1658. //
  1659. // Purpose:
  1660. // This function sends irtAPPEND_PROGRESS responses to the callback so
  1661. // that the IIMAPTransport user can report the progress of an APPEND command.
  1662. //
  1663. // Arguments:
  1664. // DWORD dwBytesUploaded [in] - number of bytes just uploaded to the
  1665. // server. This function retains a running count of bytes uploaded.
  1666. //***************************************************************************
  1667. void CImap4Agent::UploadStreamProgress(DWORD dwBytesUploaded)
  1668. {
  1669. APPEND_PROGRESS ap;
  1670. IMAP_RESPONSE irIMAPResponse;
  1671. // Check if we should report APPEND upload progress. We report if we are currently executing
  1672. // APPEND and the CRLF is waiting to be sent
  1673. if (NULL == m_piciCmdInSending || icAPPEND_COMMAND != m_piciCmdInSending->icCommandID ||
  1674. NULL == m_piciCmdInSending->pilqCmdLineQueue)
  1675. return;
  1676. else {
  1677. IMAP_LINE_FRAGMENT *pilf = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
  1678. // It's an APPEND command with non-empty linefrag queue, now check that next
  1679. // linefrag fits description for linefrag after msg body
  1680. if (NULL == pilf || iltLINE != pilf->iltFragmentType ||
  1681. ilsSTRING != pilf->ilsLiteralStoreType || 2 != pilf->dwLengthOfFragment ||
  1682. '\r' != pilf->data.pszSource[0] || '\n' != pilf->data.pszSource[1] ||
  1683. NULL != pilf->pilfNextFragment)
  1684. return;
  1685. }
  1686. // Report current progress of message upload
  1687. m_dwAppendStreamUploaded += dwBytesUploaded;
  1688. ap.dwUploaded = m_dwAppendStreamUploaded;
  1689. ap.dwTotal = m_dwAppendStreamTotal;
  1690. Assert(0 != ap.dwTotal);
  1691. Assert(ap.dwTotal >= ap.dwUploaded);
  1692. irIMAPResponse.wParam = m_piciCmdInSending->wParam;
  1693. irIMAPResponse.lParam = m_piciCmdInSending->lParam;
  1694. irIMAPResponse.hrResult = S_OK;
  1695. irIMAPResponse.lpszResponseText = NULL;
  1696. irIMAPResponse.irtResponseType = irtAPPEND_PROGRESS;
  1697. irIMAPResponse.irdResponseData.papAppendProgress = &ap;
  1698. OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse);
  1699. } // UploadStreamProgress
  1700. //***************************************************************************
  1701. // Function: OnNotify
  1702. //
  1703. // Purpose: This function is required for the IAsyncConnCB which we derive
  1704. // from (callback for CAsyncConn class). This function acts on CASyncConn
  1705. // state changes and events.
  1706. //***************************************************************************
  1707. void CImap4Agent::OnNotify(ASYNCSTATE asOld, ASYNCSTATE asNew, ASYNCEVENT ae)
  1708. {
  1709. char szLogFileLine[128];
  1710. // Check refcount, but exception is that we can get AE_CLOSE. CImap4Agent's
  1711. // destructor calls CASyncConn's Close() member, which generates one last
  1712. // message, the event AE_CLOSE, with m_lRefCount == 0.
  1713. Assert(m_lRefCount > 0 || (0 == m_lRefCount && AE_CLOSE == ae));
  1714. // Record AsyncConn event/state-change in log file
  1715. wnsprintf(szLogFileLine, ARRAYSIZE(szLogFileLine), "OnNotify: asOld = %d, asNew = %d, ae = %d",
  1716. asOld, asNew, ae);
  1717. if (m_pLogFile)
  1718. m_pLogFile->WriteLog(LOGFILE_DB, szLogFileLine);
  1719. // Check for disconnect
  1720. if (AS_DISCONNECTED == asNew) {
  1721. m_irsState = irsNOT_CONNECTED;
  1722. m_ssServerState = ssNotConnected;
  1723. m_fIDLE = FALSE;
  1724. m_bFreeToSend = TRUE;
  1725. }
  1726. // Act on async event
  1727. switch (ae) {
  1728. case AE_RECV: {
  1729. HRESULT hrResult;
  1730. // Process response lines until no more lines (hrIncomplete result)
  1731. do {
  1732. hrResult = ProcessResponseLine();
  1733. } while (SUCCEEDED(hrResult));
  1734. // If error is other than IXP_E_INCOMPLETE, drop connection
  1735. if (IXP_E_INCOMPLETE != hrResult) {
  1736. char szFailureText[MAX_RESOURCESTRING];
  1737. // Looks fatal, better warn the user that disconnection is imminent
  1738. LoadString(g_hLocRes, idsIMAPSocketReadError, szFailureText,
  1739. ARRAYSIZE(szFailureText));
  1740. OnIMAPError(hrResult, szFailureText, DONT_USE_LAST_RESPONSE);
  1741. // What else can we do but drop the connection?
  1742. DropConnection();
  1743. } // if error other than IXP_E_INCOMPLETE
  1744. break;
  1745. } // case AE_RECV
  1746. case AE_SENDDONE:
  1747. UploadStreamProgress(m_pSocket->UlGetSendByteCount());
  1748. // Received AE_SENDDONE from CAsyncConn class. We are free to send more data
  1749. m_bFreeToSend = TRUE;
  1750. ProcessSendQueue(iseSENDDONE); // Informs them that they may start sending again
  1751. break;
  1752. case AE_WRITE:
  1753. UploadStreamProgress(m_pSocket->UlGetSendByteCount());
  1754. break;
  1755. default:
  1756. CIxpBase::OnNotify(asOld, asNew, ae);
  1757. break; // case default
  1758. } // switch (ae)
  1759. } // OnNotify
  1760. //***************************************************************************
  1761. // Function: ProcessResponseLine
  1762. //
  1763. // Purpose:
  1764. // This functions handles the AE_RECV event of the OnNotify() callback.
  1765. // It gets a response line from the server (if available) and dispatches
  1766. // the line to the proper recipient based on the state of the receiver FSM.
  1767. //
  1768. // Returns:
  1769. // HRESULT indicating success or failure of CAsyncConn line retrieval.
  1770. // hrIncomplete (an error code) is returned if no more complete lines can
  1771. // be retrieved from CAsyncConn's buffer.
  1772. //***************************************************************************
  1773. HRESULT CImap4Agent::ProcessResponseLine(void)
  1774. {
  1775. HRESULT hrASyncResult;
  1776. char *pszResponseBuf;
  1777. int cbRead;
  1778. Assert(m_lRefCount > 0);
  1779. // We are always in one of two modes: line mode, or byte mode. Figure out which.
  1780. if (irsLITERAL != m_irsState && irsFETCH_BODY != m_irsState) {
  1781. // We're in line mode. Get response line from server
  1782. hrASyncResult = m_pSocket->ReadLine(&pszResponseBuf, &cbRead);
  1783. if (FAILED(hrASyncResult))
  1784. return hrASyncResult;
  1785. // Record received line in log file
  1786. if (m_pLogFile)
  1787. m_pLogFile->WriteLog(LOGFILE_RX, pszResponseBuf);
  1788. } // if-line mode
  1789. else {
  1790. // We're in literal mode. Get as many bytes as we can.
  1791. hrASyncResult = m_pSocket->ReadBytes(&pszResponseBuf,
  1792. m_dwLiteralInProgressBytesLeft, &cbRead);
  1793. if (FAILED(hrASyncResult))
  1794. return hrASyncResult;
  1795. // Update our byte count
  1796. Assert((DWORD)cbRead <= m_dwLiteralInProgressBytesLeft);
  1797. m_dwLiteralInProgressBytesLeft -= cbRead;
  1798. // Make note of received blob in log file
  1799. if (m_pLogFile) {
  1800. char szLogLine[CMDLINE_BUFSIZE];
  1801. wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Buffer (literal) of length %i", cbRead);
  1802. m_pLogFile->WriteLog(LOGFILE_RX, szLogLine);
  1803. }
  1804. } // else-not line mode
  1805. // Process it
  1806. switch (m_irsState) {
  1807. case irsUNINITIALIZED:
  1808. AssertSz(FALSE, "Attempted to use Imap4Agent class without initializing");
  1809. SafeMemFree(pszResponseBuf);
  1810. break;
  1811. case irsNOT_CONNECTED:
  1812. AssertSz(FALSE, "Received response from server when not connected");
  1813. SafeMemFree(pszResponseBuf);
  1814. break;
  1815. case irsSVR_GREETING:
  1816. ProcessServerGreeting(pszResponseBuf, cbRead);
  1817. break;
  1818. case irsIDLE: {
  1819. IMAP_RESPONSE_ID irParseResult;
  1820. CheckForCompleteResponse(pszResponseBuf, cbRead, &irParseResult);
  1821. // Check for unsolicited BYE response, and notify user of error
  1822. // Solicited BYE responses (eg, during LOGOUT cmd) can be ignored
  1823. if (irBYE_RESPONSE == irParseResult &&
  1824. IXP_AUTHRETRY != m_status &&
  1825. IXP_DISCONNECTING != m_status &&
  1826. IXP_DISCONNECTED != m_status) {
  1827. char szFailureText[MAX_RESOURCESTRING];
  1828. // Looks like an unsolicited BYE response to me
  1829. // Drop connection to avoid IXP_E_CONNECTION_DROPPED err
  1830. DropConnection();
  1831. // Report to user (sometimes server provides useful error text)
  1832. LoadString(g_hLocRes, idsIMAPUnsolicitedBYE, szFailureText,
  1833. ARRAYSIZE(szFailureText));
  1834. OnIMAPError(IXP_E_IMAP_UNSOLICITED_BYE, szFailureText,
  1835. USE_LAST_RESPONSE);
  1836. }
  1837. } // case irsIDLE
  1838. break;
  1839. case irsLITERAL:
  1840. AddBytesToLiteral(pszResponseBuf, cbRead);
  1841. break;
  1842. case irsFETCH_BODY:
  1843. DispatchFetchBodyPart(pszResponseBuf, cbRead, fFREE_BODY_TAG);
  1844. SafeMemFree(pszResponseBuf);
  1845. break;
  1846. default:
  1847. AssertSz(FALSE, "Unhandled receiver state in ProcessResponseLine()");
  1848. SafeMemFree(pszResponseBuf);
  1849. break;
  1850. } // switch (m_irsState)
  1851. return hrASyncResult;
  1852. } // ProcessResponseLine
  1853. //***************************************************************************
  1854. // Function: ProcessSendQueue
  1855. //
  1856. // Purpose:
  1857. // This function is responsible for all transmissions from the client to
  1858. // the IMAP server. It is called when certain events occur, such as the
  1859. // receipt of the AE_SENDDONE event in OnNotify().
  1860. //
  1861. // Arguments:
  1862. // IMAP_SEND_EVENT iseEvent [in] - the send event which just occurred,
  1863. // such as iseSEND_COMMAND (used to initiate a command) or
  1864. // iseCMD_CONTINUATION (when command continuation response received from
  1865. // the IMAP server).
  1866. //***************************************************************************
  1867. void CImap4Agent::ProcessSendQueue(IMAP_SEND_EVENT iseEvent)
  1868. {
  1869. boolean bFreeToSendLiteral, bFreeToUnpause;
  1870. IMAP_LINE_FRAGMENT *pilfNextFragment;
  1871. Assert(m_lRefCount > 0);
  1872. Assert(ssNotConnected < m_ssServerState);
  1873. Assert(irsNOT_CONNECTED < m_irsState);
  1874. // Initialize variables
  1875. bFreeToSendLiteral = FALSE;
  1876. bFreeToUnpause = FALSE;
  1877. // Peek at current fragment
  1878. EnterCriticalSection(&m_cs); // Reserve this NOW to avoid deadlock
  1879. EnterCriticalSection(&m_csSendQueue);
  1880. GetNextCmdToSend();
  1881. if (NULL != m_piciCmdInSending)
  1882. pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
  1883. else
  1884. pilfNextFragment = NULL;
  1885. // Act on the IMAP send event posted to us
  1886. switch (iseEvent) {
  1887. case iseSEND_COMMAND:
  1888. case iseSENDDONE:
  1889. // We don't have to do anything special for these events
  1890. break;
  1891. case iseCMD_CONTINUATION:
  1892. // Received command continuation from IMAP server. We are free to send literal
  1893. bFreeToSendLiteral = TRUE;
  1894. Assert(NULL != pilfNextFragment &&
  1895. iltLITERAL == pilfNextFragment->iltFragmentType);
  1896. break;
  1897. case iseUNPAUSE:
  1898. bFreeToUnpause = TRUE;
  1899. IxpAssert(NULL != pilfNextFragment &&
  1900. iltPAUSE == pilfNextFragment->iltFragmentType);
  1901. break;
  1902. default:
  1903. AssertSz(FALSE, "Received unknown IMAP send event");
  1904. break;
  1905. }
  1906. // Send as many fragments as we can. We must stop sending if:
  1907. // a) Any AsyncConn send command returns hrWouldBlock.
  1908. // b) The send queue is empty
  1909. // c) Next fragment is a literal and we don't have cmd continuation from svr
  1910. // d) We are at a iltPAUSE fragment, and we don't have the go-ahead to unpause
  1911. // e) We are at a iltSTOP fragment.
  1912. while (TRUE == m_bFreeToSend && NULL != pilfNextFragment &&
  1913. ((iltLITERAL != pilfNextFragment->iltFragmentType) || TRUE == bFreeToSendLiteral) &&
  1914. ((iltPAUSE != pilfNextFragment->iltFragmentType) || TRUE == bFreeToUnpause) &&
  1915. (iltSTOP != pilfNextFragment->iltFragmentType))
  1916. {
  1917. HRESULT hrResult;
  1918. int iNumBytesSent;
  1919. IMAP_LINE_FRAGMENT *pilf;
  1920. // We are free to send the next fragment, whether it's a line, literal or rangelist
  1921. // Put us into busy mode to enable the watchdog timer
  1922. if (FALSE == m_fBusy) {
  1923. hrResult = HrEnterBusy();
  1924. Assert(SUCCEEDED(hrResult));
  1925. // In retail, we want to try to continue even if HrEnterBusy failed.
  1926. }
  1927. // Send next fragment (have to check if stored as a string or a stream)
  1928. pilfNextFragment = pilfNextFragment->pilfNextFragment; // Peek at next frag
  1929. pilf = DequeueFragment(m_piciCmdInSending->pilqCmdLineQueue); // Get current frag
  1930. if (iltPAUSE == pilf->iltFragmentType) {
  1931. hrResult = S_OK; // Do nothing
  1932. }
  1933. else if (iltSTOP == pilf->iltFragmentType) {
  1934. AssertSz(FALSE, "What are we doing trying to process a STOP?");
  1935. hrResult = S_OK; // Do nothing
  1936. }
  1937. else if (iltRANGELIST == pilf->iltFragmentType) {
  1938. AssertSz(FALSE, "All rangelists should have been coalesced!");
  1939. hrResult = S_OK; // Do nothing
  1940. }
  1941. else if (ilsSTRING == pilf->ilsLiteralStoreType) {
  1942. hrResult = m_pSocket->SendBytes(pilf->data.pszSource,
  1943. pilf->dwLengthOfFragment, &iNumBytesSent);
  1944. // Record sent line in log file
  1945. if (m_pLogFile) {
  1946. // Hide the LOGIN command from logfile, for security reasons
  1947. if (icLOGIN_COMMAND != m_piciCmdInSending->icCommandID)
  1948. m_pLogFile->WriteLog(LOGFILE_TX, pilf->data.pszSource);
  1949. else
  1950. m_pLogFile->WriteLog(LOGFILE_TX, "LOGIN command sent");
  1951. }
  1952. }
  1953. else if (ilsSTREAM == pilf->ilsLiteralStoreType) {
  1954. char szLogLine[128];
  1955. // No need to rewind stream - CAsyncConn::SendStream does it for us
  1956. hrResult = m_pSocket->SendStream(pilf->data.pstmSource, &iNumBytesSent);
  1957. // Record stream size in log file
  1958. wnsprintf(szLogLine, ARRAYSIZE(szLogLine), "Stream of length %lu", pilf->dwLengthOfFragment);
  1959. if (m_pLogFile)
  1960. m_pLogFile->WriteLog(LOGFILE_TX, szLogLine);
  1961. // Record stream size for progress indication
  1962. if (icAPPEND_COMMAND == m_piciCmdInSending->icCommandID) {
  1963. m_dwAppendStreamUploaded = 0;
  1964. m_dwAppendStreamTotal = pilf->dwLengthOfFragment;
  1965. UploadStreamProgress(iNumBytesSent);
  1966. }
  1967. }
  1968. else {
  1969. AssertSz(FALSE, "What is in my send queue?");
  1970. hrResult = S_OK; // Ignore it and try to continue
  1971. }
  1972. // Clean up variables after the send
  1973. bFreeToSendLiteral = FALSE; // We've used up the cmd continuation
  1974. bFreeToUnpause = FALSE; // We've used this up, too
  1975. FreeFragment(&pilf);
  1976. // Handle errors in sending.
  1977. // If either send command returns hrWouldBlock, this means we cannot send
  1978. // more data until we receive an AE_SENDDONE event from CAsyncConn.
  1979. if (IXP_E_WOULD_BLOCK == hrResult) {
  1980. m_bFreeToSend = FALSE;
  1981. hrResult = S_OK; // $REVIEW: TEMPORARY until EricAn makes hrWouldBlock a success code
  1982. }
  1983. else if (FAILED(hrResult)) {
  1984. IMAP_RESPONSE irIMAPResponse;
  1985. char szFailureText[MAX_RESOURCESTRING];
  1986. // Send error: Report this command as terminated
  1987. irIMAPResponse.wParam = m_piciCmdInSending->wParam;
  1988. irIMAPResponse.lParam = m_piciCmdInSending->lParam;
  1989. irIMAPResponse.hrResult = hrResult;
  1990. LoadString(g_hLocRes, idsFailedIMAPCmdSend, szFailureText,
  1991. ARRAYSIZE(szFailureText));
  1992. irIMAPResponse.lpszResponseText = szFailureText;
  1993. irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
  1994. OnIMAPResponse(m_piciCmdInSending->pCBHandler, &irIMAPResponse);
  1995. }
  1996. // Are we finished with the current command?
  1997. if (NULL == pilfNextFragment || FAILED(hrResult)) {
  1998. CIMAPCmdInfo *piciFinishedCmd;
  1999. // Dequeue current command from send queue
  2000. piciFinishedCmd = DequeueCommand();
  2001. if (NULL != piciFinishedCmd) {
  2002. if (SUCCEEDED(hrResult)) {
  2003. // We successfully finished sending current command. Put it in
  2004. // list of commands waiting for a server response
  2005. AddPendingCommand(piciFinishedCmd);
  2006. Assert(NULL == pilfNextFragment);
  2007. }
  2008. else {
  2009. // Failed commands don't deserve to live
  2010. delete piciFinishedCmd;
  2011. pilfNextFragment = NULL; // No longer valid
  2012. // Drop out of busy mode
  2013. AssertSz(m_fBusy, "Check your logic, I'm calling LeaveBusy "
  2014. "although not in a busy state!");
  2015. LeaveBusy();
  2016. }
  2017. }
  2018. else {
  2019. // Hey, someone pulled the rug out!
  2020. AssertSz(FALSE, "I had this cmd... and now it's GONE!");
  2021. }
  2022. } // if (NULL == pilfNextFragment || FAILED(hrResult))
  2023. // If we finished sending current cmd, set us up to send next command
  2024. if (NULL == pilfNextFragment && NULL != m_piciSendQueue) {
  2025. GetNextCmdToSend();
  2026. if (NULL != m_piciCmdInSending)
  2027. pilfNextFragment = m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment;
  2028. }
  2029. } // while
  2030. LeaveCriticalSection(&m_csSendQueue);
  2031. LeaveCriticalSection(&m_cs);
  2032. } // ProcessSendQueue
  2033. //***************************************************************************
  2034. // Function: GetNextCmdToSend
  2035. //
  2036. // Purpose:
  2037. // This function leaves a pointer to the next command to send, in
  2038. // m_piciCmdInSending. If m_piciCmdInSending is already non-NULL (indicating
  2039. // a command in progress), then this function does nothing. Otherwise, this
  2040. // function chooses the next command from m_piciSendQueue using a set of
  2041. // rules described within.
  2042. //***************************************************************************
  2043. void CImap4Agent::GetNextCmdToSend(void)
  2044. {
  2045. CIMAPCmdInfo *pici;
  2046. // First check if we're connected
  2047. if (IXP_CONNECTED != m_status &&
  2048. IXP_AUTHORIZING != m_status &&
  2049. IXP_AUTHRETRY != m_status &&
  2050. IXP_AUTHORIZED != m_status &&
  2051. IXP_DISCONNECTING != m_status) {
  2052. Assert(NULL == m_piciCmdInSending);
  2053. return;
  2054. }
  2055. // Check if we're already in the middle of sending a command
  2056. if (NULL != m_piciCmdInSending)
  2057. return;
  2058. // Loop through the send queue looking for next eligible candidate to send
  2059. pici = m_piciSendQueue;
  2060. while (NULL != pici) {
  2061. IMAP_COMMAND icCurrentCmd;
  2062. // For a command to be sent, it must meet the following criteria:
  2063. // (1) The server must be in the correct server state. Authenticated cmds such
  2064. // as SELECT must wait until non-Authenticated cmds like LOGIN are complete.
  2065. // (2) Commands for which we want to guarantee proper wParam, lParam for their
  2066. // untagged responses cannot be streamed. See CanStreamCommand for details.
  2067. // (3) If the command is NON-UID FETCH/STORE/SEARCH or COPY, then all pending
  2068. // cmds must be NON-UID FETCH/STORE/SEARCH.
  2069. icCurrentCmd = pici->icCommandID;
  2070. if (m_ssServerState >= pici->ssMinimumState && CanStreamCommand(icCurrentCmd)) {
  2071. if ((icFETCH_COMMAND == icCurrentCmd || icSTORE_COMMAND == icCurrentCmd ||
  2072. icSEARCH_COMMAND == icCurrentCmd || icCOPY_COMMAND == icCurrentCmd) &&
  2073. FALSE == pici->fUIDRangeList) {
  2074. if (isValidNonWaitingCmdSequence())
  2075. break; // This command is good to go
  2076. }
  2077. else
  2078. break; // This command is good to go
  2079. }
  2080. // Advance ptr to next command
  2081. pici = pici->piciNextCommand;
  2082. } // while
  2083. // If we found a command, coalesce its iltLINE and iltRANGELIST elements
  2084. if (NULL != pici) {
  2085. CompressCommand(pici);
  2086. m_piciCmdInSending = pici;
  2087. }
  2088. } // GetNextCmdToSend
  2089. //***************************************************************************
  2090. // Function: CanStreamCommand
  2091. //
  2092. // Purpose:
  2093. // This function determines whether or not the given command can be
  2094. // streamed. All commands can be streamed except for the following:
  2095. // SELECT, EXAMINE, LIST, LSUB and SEARCH.
  2096. //
  2097. // SELECT and EXAMINE cannot be streamed because it doesn't make much sense
  2098. // to allow that.
  2099. // LIST, LSUB and SEARCH cannot be streamed because we want to guarantee
  2100. // that we can identify the correct wParam, lParam and pCBHandler when we call
  2101. // OnResponse for their untagged responses.
  2102. //
  2103. // Arguments:
  2104. // IMAP_COMMAND icCommandID [in] - the command which you would like to send
  2105. // to the server.
  2106. //
  2107. // Returns:
  2108. // TRUE if the given command may be sent. FALSE if you cannot send the
  2109. // given command at this time (try again later).
  2110. //***************************************************************************
  2111. boolean CImap4Agent::CanStreamCommand(IMAP_COMMAND icCommandID)
  2112. {
  2113. boolean fResult;
  2114. WORD wNumberOfMatches;
  2115. fResult = TRUE;
  2116. wNumberOfMatches = 0;
  2117. switch (icCommandID) {
  2118. // We don't stream any of the following commands
  2119. case icSELECT_COMMAND:
  2120. case icEXAMINE_COMMAND:
  2121. wNumberOfMatches = FindTransactionID(NULL, NULL, NULL,
  2122. icSELECT_COMMAND, icEXAMINE_COMMAND);
  2123. break;
  2124. case icLIST_COMMAND:
  2125. wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLIST_COMMAND);
  2126. break;
  2127. case icLSUB_COMMAND:
  2128. wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icLSUB_COMMAND);
  2129. break;
  2130. case icSEARCH_COMMAND:
  2131. wNumberOfMatches = FindTransactionID(NULL, NULL, NULL, icSEARCH_COMMAND);
  2132. break;
  2133. } //switch
  2134. if (wNumberOfMatches > 0)
  2135. fResult = FALSE;
  2136. return fResult;
  2137. } // CanStreamCommand
  2138. //***************************************************************************
  2139. // Function: isValidNonWaitingCmdSequence
  2140. //
  2141. // Purpose:
  2142. // This function is called whenever we would like to send a FETCH, STORE,
  2143. // SEARCH or COPY command (all NON-UID) to the server. These commands are
  2144. // subject to waiting rules as discussed in section 5.5 of RFC2060.
  2145. //
  2146. // Returns:
  2147. // TRUE if the non-UID FETCH/STORE/SEARCH/COPY command can be sent at
  2148. // this time. FALSE if the command cannot be sent at this time (try again
  2149. // later).
  2150. //***************************************************************************
  2151. boolean CImap4Agent::isValidNonWaitingCmdSequence(void)
  2152. {
  2153. CIMAPCmdInfo *pici;
  2154. boolean fResult;
  2155. // Loop through the list of pending commands
  2156. pici = m_piciPendingList;
  2157. fResult = TRUE;
  2158. while (NULL != pici) {
  2159. IMAP_COMMAND icCurrentCmd;
  2160. // non-UID FETCH/STORE/SEARCH/COPY can only execute if the only
  2161. // pending commands are non-UID FETCH/STORE/SEARCH.
  2162. icCurrentCmd = pici->icCommandID;
  2163. if (icFETCH_COMMAND != icCurrentCmd &&
  2164. icSTORE_COMMAND != icCurrentCmd &&
  2165. icSEARCH_COMMAND != icCurrentCmd ||
  2166. pici->fUIDRangeList) {
  2167. fResult = FALSE;
  2168. break;
  2169. }
  2170. // Advance pointer to next command
  2171. pici = pici->piciNextCommand;
  2172. } // while
  2173. return fResult;
  2174. } // isValidNonWaitingCmdSequence
  2175. //***************************************************************************
  2176. // Function: CompressCommand
  2177. //
  2178. // Purpose: This function walks through the given command's linefrag queue
  2179. // and combines all sequential iltLINE and iltRANGELIST linefrag elements
  2180. // into a single iltLINE element for transmitting purposes. The reason we
  2181. // have to combine these is because I had a pipe dream once that CImap4Agent
  2182. // would auto-detect EXPUNGE responses and modify all iltRANGELIST elements
  2183. // in m_piciSendQueue to reflect the new msg seq num reality. Who knows,
  2184. // it might even come true some day.
  2185. //
  2186. // When it does come true, this function can still exist: once a command
  2187. // enters m_piciCmdInSending, it is too late to modify its rangelist.
  2188. //
  2189. // Arguments:
  2190. // CIMAPCmdInfo *pici [in] - pointer to the IMAP command to compress.
  2191. //***************************************************************************
  2192. void CImap4Agent::CompressCommand(CIMAPCmdInfo *pici)
  2193. {
  2194. IMAP_LINE_FRAGMENT *pilfCurrent, *pilfStartOfRun, *pilfPreStartOfRun;
  2195. HRESULT hrResult;
  2196. // Codify assumptions
  2197. Assert(NULL != pici);
  2198. Assert(5 == iltLAST); // If this changes, update this function
  2199. // Initialize variables
  2200. hrResult = S_OK;
  2201. pilfCurrent = pici->pilqCmdLineQueue->pilfFirstFragment;
  2202. pilfStartOfRun = pilfCurrent;
  2203. pilfPreStartOfRun = NULL; // Points to linefrag element before pilfStartOfRun
  2204. while (1) {
  2205. if (NULL == pilfCurrent ||
  2206. (iltLINE != pilfCurrent->iltFragmentType &&
  2207. iltRANGELIST != pilfCurrent->iltFragmentType)) {
  2208. // We've hit a non-coalescable linefrag, coalesce previous run
  2209. // We only coalesce runs which are greater than one linefrag element
  2210. if (NULL != pilfStartOfRun && pilfCurrent != pilfStartOfRun->pilfNextFragment) {
  2211. IMAP_LINE_FRAGMENT *pilf, *pilfSuperLine;
  2212. CByteStream bstmCmdLine;
  2213. // Run length > 1, coalesce the entire run
  2214. pilf = pilfStartOfRun;
  2215. while (pilf != pilfCurrent) {
  2216. if (iltLINE == pilf->iltFragmentType) {
  2217. hrResult = bstmCmdLine.Write(pilf->data.pszSource,
  2218. pilf->dwLengthOfFragment, NULL);
  2219. if (FAILED(hrResult))
  2220. goto exit;
  2221. }
  2222. else {
  2223. LPSTR pszMsgRange;
  2224. DWORD dwLengthOfString;
  2225. // Convert rangelist to string
  2226. Assert(iltRANGELIST == pilf->iltFragmentType);
  2227. hrResult = pilf->data.prlRangeList->
  2228. RangeToIMAPString(&pszMsgRange, &dwLengthOfString);
  2229. if (FAILED(hrResult))
  2230. goto exit;
  2231. hrResult = bstmCmdLine.Write(pszMsgRange, dwLengthOfString, NULL);
  2232. MemFree(pszMsgRange);
  2233. if (FAILED(hrResult))
  2234. goto exit;
  2235. // Append a space behind the rangelist
  2236. hrResult = bstmCmdLine.Write(g_szSpace, 1, NULL);
  2237. if (FAILED(hrResult))
  2238. goto exit;
  2239. } // else
  2240. pilf = pilf->pilfNextFragment;
  2241. } // while (pilf != pilfCurrent)
  2242. // OK, now we've coalesced the run data into a stream
  2243. // Create a iltLINE fragment to hold the super-string
  2244. pilfSuperLine = new IMAP_LINE_FRAGMENT;
  2245. if (NULL == pilfSuperLine) {
  2246. hrResult = E_OUTOFMEMORY;
  2247. goto exit;
  2248. }
  2249. pilfSuperLine->iltFragmentType = iltLINE;
  2250. pilfSuperLine->ilsLiteralStoreType = ilsSTRING;
  2251. hrResult = bstmCmdLine.HrAcquireStringA(&pilfSuperLine->dwLengthOfFragment,
  2252. &pilfSuperLine->data.pszSource, ACQ_DISPLACE);
  2253. if (FAILED(hrResult)) {
  2254. delete pilfSuperLine;
  2255. goto exit;
  2256. }
  2257. // OK, we've created the uber-line, now link it into list
  2258. pilfSuperLine->pilfNextFragment = pilfCurrent;
  2259. pilfSuperLine->pilfPrevFragment = pilfPreStartOfRun;
  2260. Assert(pilfPreStartOfRun == pilfStartOfRun->pilfPrevFragment);
  2261. if (NULL == pilfPreStartOfRun)
  2262. // Insert at head of queue
  2263. pici->pilqCmdLineQueue->pilfFirstFragment = pilfSuperLine;
  2264. else
  2265. pilfPreStartOfRun->pilfNextFragment = pilfSuperLine;
  2266. // Special case: if pilfCurrent is NULL, pilfSuperLine is new last frag
  2267. if (NULL == pilfCurrent)
  2268. pici->pilqCmdLineQueue->pilfLastFragment = pilfSuperLine;
  2269. // Free the old run of linefrag elements
  2270. pilf = pilfStartOfRun;
  2271. while(pilf != pilfCurrent) {
  2272. IMAP_LINE_FRAGMENT *pilfNext;
  2273. pilfNext = pilf->pilfNextFragment;
  2274. FreeFragment(&pilf);
  2275. pilf = pilfNext;
  2276. } // while(pilf != pilfCurrent)
  2277. } // if run length > 1
  2278. // Start collecting line fragments for next coalescing run
  2279. if (NULL != pilfCurrent) {
  2280. pilfStartOfRun = pilfCurrent->pilfNextFragment;
  2281. pilfPreStartOfRun = pilfCurrent;
  2282. } // if
  2283. } // if current linefrag is non-coalescable
  2284. // Advance to next line fragment
  2285. if (NULL != pilfCurrent)
  2286. pilfCurrent = pilfCurrent->pilfNextFragment;
  2287. else
  2288. break; // Our work here is done
  2289. } // while (NULL != pilfCurrent)
  2290. exit:
  2291. AssertSz(SUCCEEDED(hrResult), "Could not compress an IMAP command");
  2292. } // CompressCommand
  2293. //***************************************************************************
  2294. // Function: SendCmdLine
  2295. //
  2296. // Purpose:
  2297. // This function enqueues an IMAP line fragment (as opposed to an IMAP
  2298. // literal fragment) on the send queue of the given CIMAPCmdInfo structure.
  2299. // The insertion point can either be in front
  2300. // All IMAP commands are constructed in full before being submitted to the
  2301. // send machinery, so this function does not actually transmit anything.
  2302. //
  2303. // Arguments:
  2304. // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
  2305. // the IMAP command currently under construction.
  2306. // DWORD dwFlags [in] - various options:
  2307. // sclINSERT_BEFORE_PAUSE: line fragment will be inserted before the
  2308. // first iltPAUSE fragment in the queue. It is the
  2309. // caller's responsibility to ensure that a iltPAUSE
  2310. // fragment exists.
  2311. // sclAPPEND_TO_END: (DEFAULT CASE, there is no flag for this) line fragment
  2312. // will be appended to the end of the queue.
  2313. // sclAPPEND_CRLF: Appends CRLF to contents of lpszCommandText when
  2314. // constructing the line fragment.
  2315. //
  2316. // LPCSTR lpszCommandText [in] - a pointer to the line fragment to enqueue.
  2317. // The first line fragment of all commands should include a tag. This
  2318. // function does not provide command tags, and does not append CRLF to
  2319. // the end of each line by default (see sclAPPEND_CRLF above).
  2320. // DWORD dwCmdLineLength [in] - the length of the text pointed to by
  2321. // lpszCommandText.
  2322. //
  2323. // Returns:
  2324. // HRESULT indicating success or failure.
  2325. //***************************************************************************
  2326. HRESULT CImap4Agent::SendCmdLine(CIMAPCmdInfo *piciCommand, DWORD dwFlags,
  2327. LPCSTR lpszCommandText, DWORD dwCmdLineLength)
  2328. {
  2329. IMAP_LINE_FRAGMENT *pilfLine;
  2330. BOOL bResult;
  2331. BOOL fAppendCRLF;
  2332. Assert(m_lRefCount > 0);
  2333. Assert(NULL != piciCommand);
  2334. Assert(NULL != lpszCommandText);
  2335. // Create and fill out a line fragment element
  2336. fAppendCRLF = !!(dwFlags & sclAPPEND_CRLF);
  2337. pilfLine = new IMAP_LINE_FRAGMENT;
  2338. if (NULL == pilfLine)
  2339. return E_OUTOFMEMORY;
  2340. pilfLine->iltFragmentType = iltLINE;
  2341. pilfLine->ilsLiteralStoreType = ilsSTRING;
  2342. pilfLine->dwLengthOfFragment = dwCmdLineLength + (fAppendCRLF ? 2 : 0);
  2343. pilfLine->pilfNextFragment = NULL;
  2344. pilfLine->pilfPrevFragment = NULL;
  2345. DWORD cchSize = (pilfLine->dwLengthOfFragment + 1); // Room for null-term
  2346. bResult = MemAlloc((void **)&pilfLine->data.pszSource, cchSize * sizeof(pilfLine->data.pszSource[0]));
  2347. if (FALSE == bResult)
  2348. {
  2349. delete pilfLine;
  2350. return E_OUTOFMEMORY;
  2351. }
  2352. CopyMemory(pilfLine->data.pszSource, lpszCommandText, dwCmdLineLength);
  2353. if (fAppendCRLF)
  2354. StrCpyN(pilfLine->data.pszSource + dwCmdLineLength, c_szCRLF, cchSize - dwCmdLineLength);
  2355. else
  2356. *(pilfLine->data.pszSource + dwCmdLineLength) = '\0'; // Null-terminate the line
  2357. // Queue it up
  2358. if (dwFlags & sclINSERT_BEFORE_PAUSE) {
  2359. InsertFragmentBeforePause(pilfLine, piciCommand->pilqCmdLineQueue);
  2360. ProcessSendQueue(iseSEND_COMMAND); // Pump send queue in this case
  2361. }
  2362. else
  2363. EnqueueFragment(pilfLine, piciCommand->pilqCmdLineQueue);
  2364. return S_OK;
  2365. } // SendCmdLine
  2366. //***************************************************************************
  2367. // Function: SendLiteral
  2368. //
  2369. // Purpose:
  2370. // This function enqueues an IMAP literal fragment (as opposed to an IMAP
  2371. // line fragment) on the send queue of the given CIMAPCmdInfo structure.
  2372. // All IMAP commands are constructed in full before being submitted to the
  2373. // send machinery, so this function does not actually transmit anything.
  2374. //
  2375. // Arguments:
  2376. // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
  2377. // the IMAP command currently under construction.
  2378. // LPSTREAM pstmLiteral [in] - a pointer to the stream containing the
  2379. // literal to be sent.
  2380. // DWORD dwSizeOfStream [in] - the size of the stream.
  2381. //
  2382. // Returns:
  2383. // HRESULT indicating success or failure.
  2384. //***************************************************************************
  2385. HRESULT CImap4Agent::SendLiteral(CIMAPCmdInfo *piciCommand,
  2386. LPSTREAM pstmLiteral, DWORD dwSizeOfStream)
  2387. {
  2388. IMAP_LINE_FRAGMENT *pilfLiteral;
  2389. Assert(m_lRefCount > 0);
  2390. Assert(NULL != pstmLiteral);
  2391. // Create and fill out a fragment structure for the literal
  2392. pilfLiteral = new IMAP_LINE_FRAGMENT;
  2393. if (NULL == pilfLiteral)
  2394. return E_OUTOFMEMORY;
  2395. pilfLiteral->iltFragmentType = iltLITERAL;
  2396. pilfLiteral->ilsLiteralStoreType = ilsSTREAM;
  2397. pilfLiteral->dwLengthOfFragment = dwSizeOfStream;
  2398. pstmLiteral->AddRef(); // We're about to make a copy of this
  2399. pilfLiteral->data.pstmSource = pstmLiteral;
  2400. pilfLiteral->pilfNextFragment = NULL;
  2401. pilfLiteral->pilfPrevFragment = NULL;
  2402. // Queue it up to send out when we receive command continuation from svr
  2403. EnqueueFragment(pilfLiteral, piciCommand->pilqCmdLineQueue);
  2404. return S_OK;
  2405. } // SendLiteral
  2406. //***************************************************************************
  2407. // Function: SendRangelist
  2408. //
  2409. // Purpose:
  2410. // This function enqueues a rangelist on the send queue of the given
  2411. // CIMAPCmdInfo structure. All IMAP commands are constructed in full before
  2412. // being submitted to the send machinery, so this function does not actually
  2413. // transmit anything. The reason for storing rangelists is so that if the
  2414. // rangelist represents a message sequence number range, we can resequence it
  2415. // if we receive EXPUNGE responses before the command is transmitted.
  2416. //
  2417. // Arguments:
  2418. // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
  2419. // the IMAP command currently under construction.
  2420. // IRangeList *prlRangeList [in] - rangelist which will be converted to an
  2421. // IMAP message set during command transmission.
  2422. // boolean bUIDRangeList [in] - TRUE if rangelist represents a UID message
  2423. // set, FALSE if it represents a message sequence number message set.
  2424. // UID message sets are not subject to resequencing after an EXPUNGE
  2425. // response is received from the server.
  2426. //
  2427. // Returns:
  2428. // HRESULT indicating success or failure.
  2429. //***************************************************************************
  2430. HRESULT CImap4Agent::SendRangelist(CIMAPCmdInfo *piciCommand,
  2431. IRangeList *prlRangeList, boolean bUIDRangeList)
  2432. {
  2433. IMAP_LINE_FRAGMENT *pilfRangelist;
  2434. Assert(m_lRefCount > 0);
  2435. Assert(NULL != piciCommand);
  2436. Assert(NULL != prlRangeList);
  2437. // Create and fill out a rangelist element
  2438. pilfRangelist = new IMAP_LINE_FRAGMENT;
  2439. if (NULL == pilfRangelist)
  2440. return E_OUTOFMEMORY;
  2441. pilfRangelist->iltFragmentType = iltRANGELIST;
  2442. pilfRangelist->ilsLiteralStoreType = ilsSTRING;
  2443. pilfRangelist->dwLengthOfFragment = 0;
  2444. pilfRangelist->pilfNextFragment = NULL;
  2445. pilfRangelist->pilfPrevFragment = NULL;
  2446. prlRangeList->AddRef();
  2447. pilfRangelist->data.prlRangeList = prlRangeList;
  2448. // Queue it up
  2449. EnqueueFragment(pilfRangelist, piciCommand->pilqCmdLineQueue);
  2450. return S_OK;
  2451. } // SendRangelist
  2452. //***************************************************************************
  2453. // Function: SendPause
  2454. //
  2455. // Purpose:
  2456. // This function enqueues a pause on the send queue of the given
  2457. // CIMAPCmdInfo structure. All IMAP commands are constructed in full before
  2458. // being submitted to the send machinery, so this function does not actually
  2459. // transmit anything. A pause is used to freeze the send queue until we signal
  2460. // it to proceed again. It is used in commands which involve bi-directional
  2461. // communication, such as AUTHENTICATE or the IDLE extension.
  2462. //
  2463. // Arguments:
  2464. // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
  2465. // the IMAP command currently under construction.
  2466. //
  2467. // Returns:
  2468. // HRESULT indicating success or failure.
  2469. //***************************************************************************
  2470. HRESULT CImap4Agent::SendPause(CIMAPCmdInfo *piciCommand)
  2471. {
  2472. IMAP_LINE_FRAGMENT *pilfPause;
  2473. Assert(m_lRefCount > 0);
  2474. Assert(NULL != piciCommand);
  2475. // Create and fill out a pause element
  2476. pilfPause = new IMAP_LINE_FRAGMENT;
  2477. if (NULL == pilfPause)
  2478. return E_OUTOFMEMORY;
  2479. pilfPause->iltFragmentType = iltPAUSE;
  2480. pilfPause->ilsLiteralStoreType = ilsSTRING;
  2481. pilfPause->dwLengthOfFragment = 0;
  2482. pilfPause->pilfNextFragment = NULL;
  2483. pilfPause->pilfPrevFragment = NULL;
  2484. pilfPause->data.pszSource = NULL;
  2485. // Queue it up
  2486. EnqueueFragment(pilfPause, piciCommand->pilqCmdLineQueue);
  2487. return S_OK;
  2488. } // SendPause
  2489. //***************************************************************************
  2490. // Function: SendStop
  2491. //
  2492. // Purpose:
  2493. // This function enqueues a STOP on the send queue of the given
  2494. // CIMAPCmdInfo structure. All IMAP commands are constructed in full before
  2495. // being submitted to the send machinery, so this function does not actually
  2496. // transmit anything. A STOP is used to freeze the send queue until that
  2497. // command is removed from the send queue by tagged command completion.
  2498. // Currently only used in the IDLE command, because we don't want to send
  2499. // any commands until the server indicates that we are out of IDLE mode.
  2500. //
  2501. // Arguments:
  2502. // CIMAPCmdInfo *piciCommand [in] - pointer to an info structure describing
  2503. // the IMAP command currently under construction.
  2504. //
  2505. // Returns:
  2506. // HRESULT indicating success or failure.
  2507. //***************************************************************************
  2508. HRESULT CImap4Agent::SendStop(CIMAPCmdInfo *piciCommand)
  2509. {
  2510. IMAP_LINE_FRAGMENT *pilfStop;
  2511. Assert(m_lRefCount > 0);
  2512. Assert(NULL != piciCommand);
  2513. // Create and fill out a stop element
  2514. pilfStop = new IMAP_LINE_FRAGMENT;
  2515. if (NULL == pilfStop)
  2516. return E_OUTOFMEMORY;
  2517. pilfStop->iltFragmentType = iltSTOP;
  2518. pilfStop->ilsLiteralStoreType = ilsSTRING;
  2519. pilfStop->dwLengthOfFragment = 0;
  2520. pilfStop->pilfNextFragment = NULL;
  2521. pilfStop->pilfPrevFragment = NULL;
  2522. pilfStop->data.pszSource = NULL;
  2523. // Queue it up
  2524. EnqueueFragment(pilfStop, piciCommand->pilqCmdLineQueue);
  2525. return S_OK;
  2526. } // SendStop
  2527. //***************************************************************************
  2528. // Function: ParseSvrResponseLine
  2529. //
  2530. // Purpose:
  2531. // Given a line, this function classifies the line
  2532. // as an untagged response, a command continuation, or a tagged response.
  2533. // Depending on the classification, the line is then dispatched to helper
  2534. // functions to parse and act on the line.
  2535. //
  2536. // Arguments:
  2537. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - IMAP line fragment to parse.
  2538. // The given pointer is updated so that it always points to the last
  2539. // IMAP line fragment processed. The caller need only free this fragment.
  2540. // All previous fragments will have already been freed.
  2541. // boolean *lpbTaggedResponse [out] - sets to TRUE if response is tagged.
  2542. // LPSTR lpszTagFromSvr [out] - returns tag here if response was tagged.
  2543. // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
  2544. // if we recognized it. Otherwise returns irNONE.
  2545. //
  2546. // Returns:
  2547. // HRESULT indicating success or failure.
  2548. //***************************************************************************
  2549. HRESULT CImap4Agent::ParseSvrResponseLine (IMAP_LINE_FRAGMENT **ppilfLine,
  2550. boolean *lpbTaggedResponse,
  2551. LPSTR lpszTagFromSvr,
  2552. IMAP_RESPONSE_ID *pirParseResult)
  2553. {
  2554. LPSTR p, lpszSvrResponseLine;
  2555. HRESULT hrResult;
  2556. // Check arguments
  2557. Assert(m_lRefCount > 0);
  2558. Assert(NULL != ppilfLine);
  2559. Assert(NULL != *ppilfLine);
  2560. Assert(NULL != lpbTaggedResponse);
  2561. Assert(NULL != lpszTagFromSvr);
  2562. Assert(NULL != pirParseResult);
  2563. *lpbTaggedResponse = FALSE; // Assume untagged response to start
  2564. *pirParseResult = irNONE;
  2565. // Make sure we have a line fragment, not a literal
  2566. if (iltLINE != (*ppilfLine)->iltFragmentType) {
  2567. AssertSz(FALSE, "I was passed a literal to parse!");
  2568. return IXP_E_IMAP_RECVR_ERROR;
  2569. }
  2570. else
  2571. lpszSvrResponseLine = (*ppilfLine)->data.pszSource;
  2572. // Determine if server response was command continuation, untagged or tagged
  2573. // Look at first character of response line to figure it out
  2574. hrResult = S_OK;
  2575. p = lpszSvrResponseLine + 1;
  2576. switch(*lpszSvrResponseLine) {
  2577. case cCOMMAND_CONTINUATION_PREFIX:
  2578. if (NULL != m_piciCmdInSending &&
  2579. icAUTHENTICATE_COMMAND == m_piciCmdInSending->icCommandID) {
  2580. LPSTR pszStartOfData;
  2581. DWORD dwLengthOfData;
  2582. if ((*ppilfLine)->dwLengthOfFragment <= 2) {
  2583. pszStartOfData = NULL;
  2584. dwLengthOfData = 0;
  2585. }
  2586. else {
  2587. pszStartOfData = p + 1;
  2588. dwLengthOfData = (*ppilfLine)->dwLengthOfFragment - 2;
  2589. }
  2590. AuthenticateUser(aeCONTINUE, pszStartOfData, dwLengthOfData);
  2591. }
  2592. else if (NULL != m_piciCmdInSending &&
  2593. icIDLE_COMMAND == m_piciCmdInSending->icCommandID) {
  2594. // Leave busy mode, as we may be sitting idle for some time
  2595. LeaveBusy();
  2596. m_fIDLE = TRUE; // We are now in IDLE mode
  2597. // Check if any commands are waiting to be sent
  2598. if ((NULL != m_piciCmdInSending) && (NULL != m_piciCmdInSending->piciNextCommand))
  2599. ProcessSendQueue(iseUNPAUSE); // Let's get out of IDLE
  2600. }
  2601. else {
  2602. // Literal continuation response
  2603. // Save response text - assume space follows "+", no big deal if it doesn't
  2604. StrCpyN(m_szLastResponseText, p + 1, ARRAYSIZE(m_szLastResponseText));
  2605. ProcessSendQueue(iseCMD_CONTINUATION); // Go ahead and send the literal
  2606. }
  2607. break; // case cCOMMAND_CONTINUATION_PREFIX
  2608. case cUNTAGGED_RESPONSE_PREFIX:
  2609. if (cSPACE == *p) {
  2610. // Server response fits spec'd form, parse as untagged response
  2611. p += 1; // Advance p to point to next word
  2612. // Untagged responses can be status, server/mailbox status or
  2613. // message status responses.
  2614. // Check for message status responses, first, by seeing
  2615. // if first char of next word is a number
  2616. // *** Consider using isdigit or IsDigit? ***
  2617. // Assert(FALSE) (placeholder)
  2618. if (*p >= '0' && *p <= '9')
  2619. hrResult = ParseMsgStatusResponse(ppilfLine, p, pirParseResult);
  2620. else {
  2621. // It wasn't a msg status response, try status response
  2622. hrResult = ParseStatusResponse(p, pirParseResult);
  2623. // Check for error. The only error we ignore in this case is
  2624. // IXP_E_IMAP_UNRECOGNIZED_RESP, since this only means we
  2625. // should try to parse as a server/mailbox response
  2626. if (FAILED(hrResult) &&
  2627. IXP_E_IMAP_UNRECOGNIZED_RESP != hrResult)
  2628. break;
  2629. if (irNONE == *pirParseResult)
  2630. // It wasn't a status response, check if it's server/mailbox resp
  2631. hrResult = ParseSvrMboxResponse(ppilfLine, p, pirParseResult);
  2632. }
  2633. } // if(cSPACE == *p)
  2634. else
  2635. // Must be a garbled response line
  2636. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  2637. break; // case cUNTAGGED_RESPONSE_PREFIX
  2638. default:
  2639. // Assume it's a tagged response
  2640. // Check if response line is big enough to hold one of our tags
  2641. if ((*ppilfLine)->dwLengthOfFragment <= NUM_TAG_CHARS) {
  2642. hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
  2643. break;
  2644. }
  2645. // Skip past tag and check for the space
  2646. p = lpszSvrResponseLine + NUM_TAG_CHARS;
  2647. if (cSPACE == *p) {
  2648. // Server response fits spec'd form, parse status response
  2649. *p = '\0'; // Null-terminate at the tag, so we can retrieve it
  2650. // Inform caller that this response was tagged, and return tag
  2651. *lpbTaggedResponse = TRUE;
  2652. StrCpyN(lpszTagFromSvr, lpszSvrResponseLine, TAG_BUFSIZE);
  2653. // Now process and return status response
  2654. hrResult = ParseStatusResponse(p + 1, pirParseResult);
  2655. }
  2656. else
  2657. // Must be a garbled response line
  2658. hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
  2659. break; // case DEFAULT (assumed to be tagged)
  2660. } // switch (*lpszSvrResponseLine)
  2661. // If an error occurred, return contents of the last processed fragment
  2662. if (FAILED(hrResult))
  2663. StrCpyN(m_szLastResponseText, (*ppilfLine)->data.pszSource, ARRAYSIZE(m_szLastResponseText));
  2664. return hrResult;
  2665. } // ParseSvrResponseLine
  2666. //***************************************************************************
  2667. // Function: ParseStatusResponse
  2668. //
  2669. // Purpose:
  2670. // This function parses and acts on Status Responses (section 7.1 of
  2671. // RFC-1730) (ie, OK/NO/BAD/PREAUTH/BYE). Response codes (eg, ALERT,
  2672. // TRYCREATE) are dispatched to a helper function, ParseResponseCode, for
  2673. // processing. The human-readable text associated with the response is
  2674. // stored in the module variable, m_szLastResponseText.
  2675. //
  2676. // Arguments:
  2677. // LPSTR lpszStatusResponseLine [in] - a pointer to the text which possibly
  2678. // represents a status response. The text should not include the first
  2679. // part of the line, which identifies the response as tagged ("0000 ")
  2680. // or untagged ("* ").
  2681. // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
  2682. // if we recognized it. Otherwise does not write a value out. The caller
  2683. // must initialize this variable to irNONE before calling this function.
  2684. //
  2685. // Returns:
  2686. // HRESULT indicating success or failure. If this function identifies the
  2687. // response as a status response, it returns S_OK. If it cannot recognize
  2688. // the response, it returns IXP_E_IMAP_UNRECOGNIZED_RESP. If we recognize
  2689. // the response but not the response CODE, it returns
  2690. // IXP_S_IMAP_UNRECOGNIZED_RESP (success code because we don't want
  2691. // to ever send an error to user based on unrecognized response code).
  2692. //***************************************************************************
  2693. HRESULT CImap4Agent::ParseStatusResponse (LPSTR lpszStatusResponseLine,
  2694. IMAP_RESPONSE_ID *pirParseResult)
  2695. {
  2696. HRESULT hrResult;
  2697. LPSTR lpszResponseText;
  2698. // Check arguments
  2699. Assert(m_lRefCount > 0);
  2700. Assert(NULL != lpszStatusResponseLine);
  2701. Assert(NULL != pirParseResult);
  2702. Assert(irNONE == *pirParseResult);
  2703. hrResult = S_OK;
  2704. // We can distinguish between all status responses by looking at second char
  2705. // First, determine that string is at least 1 character long
  2706. if ('\0' == *lpszStatusResponseLine)
  2707. return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a status response that we understand
  2708. lpszResponseText = lpszStatusResponseLine;
  2709. switch (*(lpszStatusResponseLine+1)) {
  2710. int iResult;
  2711. case 'k':
  2712. case 'K': // Possibly the "OK" Status response
  2713. iResult = StrCmpNI(lpszStatusResponseLine, "OK ", 3);
  2714. if (0 == iResult) {
  2715. // Definitely an "OK" status response
  2716. *pirParseResult = irOK_RESPONSE;
  2717. lpszResponseText += 3;
  2718. }
  2719. break; // case 'K' for possible "OK"
  2720. case 'o':
  2721. case 'O': // Possibly the "NO" status response
  2722. iResult = StrCmpNI(lpszStatusResponseLine, "NO ", 3);
  2723. if (0 == iResult) {
  2724. // Definitely a "NO" response
  2725. *pirParseResult = irNO_RESPONSE;
  2726. lpszResponseText += 3;
  2727. }
  2728. break; // case 'O' for possible "NO"
  2729. case 'a':
  2730. case 'A': // Possibly the "BAD" status response
  2731. iResult = StrCmpNI(lpszStatusResponseLine, "BAD ", 4);
  2732. if (0 == iResult) {
  2733. // Definitely a "BAD" response
  2734. *pirParseResult = irBAD_RESPONSE;
  2735. lpszResponseText += 4;
  2736. }
  2737. break; // case 'A' for possible "BAD"
  2738. case 'r':
  2739. case 'R': // Possibly the "PREAUTH" status response
  2740. iResult = StrCmpNI(lpszStatusResponseLine, "PREAUTH ", 8);
  2741. if (0 == iResult) {
  2742. // Definitely a "PREAUTH" response:
  2743. // PREAUTH is issued only as a greeting - check for proper context
  2744. // If improper context, ignore PREAUTH response
  2745. if (ssConnecting == m_ssServerState) {
  2746. *pirParseResult = irPREAUTH_RESPONSE;
  2747. lpszResponseText += 8;
  2748. m_ssServerState = ssAuthenticated;
  2749. }
  2750. }
  2751. break; // case 'R' for possible "PREAUTH"
  2752. case 'y':
  2753. case 'Y': // Possibly the "BYE" status response
  2754. iResult = StrCmpNI(lpszStatusResponseLine, "BYE ", 4);
  2755. if (0 == iResult) {
  2756. // Definitely a "BYE" response:
  2757. // Set server state to not connected
  2758. *pirParseResult = irBYE_RESPONSE;
  2759. lpszResponseText += 4;
  2760. m_ssServerState = ssNotConnected;
  2761. }
  2762. break; // case 'Y' for possible "BYE"
  2763. } // switch (*(lpszStatusResponseLine+1))
  2764. // If we recognized the command, proceed to process the response code
  2765. if (SUCCEEDED(hrResult) && irNONE != *pirParseResult) {
  2766. // We recognized the command, so lpszResponseText points to resp_text
  2767. // as defined in RFC-1730. Look for optional response code
  2768. if ('[' == *lpszResponseText) {
  2769. HRESULT hrResponseCodeResult;
  2770. hrResponseCodeResult = ParseResponseCode(lpszResponseText + 1);
  2771. if (FAILED(hrResponseCodeResult))
  2772. hrResult = hrResponseCodeResult;
  2773. }
  2774. else
  2775. // No response code, record response text for future retrieval
  2776. StrCpyN(m_szLastResponseText, lpszResponseText, ARRAYSIZE(m_szLastResponseText));
  2777. }
  2778. // If we didn't recognize the command, translate hrResult
  2779. if (SUCCEEDED(hrResult) && irNONE == *pirParseResult)
  2780. hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
  2781. return hrResult;
  2782. } // ParseStatusResponse
  2783. //***************************************************************************
  2784. // Function: ParseResponseCode
  2785. //
  2786. // Purpose:
  2787. // This function parses and acts on the response code which may be
  2788. // returned with a status response (eg, PERMANENTFLAGS or ALERT). It is
  2789. // called by ParseStatusResponse upon detection of a response code. This
  2790. // function saves the human-readable text of the response code to
  2791. // m_szLastResponseLine.
  2792. //
  2793. // Arguments:
  2794. // LPSTR lpszResponseCode [in] - a pointer to the response code portion
  2795. // of a response line, omitting the opening bracket ("[").
  2796. //
  2797. // Returns:
  2798. // HRESULT indicating success or failure. If we cannot recognize the
  2799. // response code, we return IXP_S_IMAP_UNRECOGNIZED_RESP.
  2800. //***************************************************************************
  2801. HRESULT CImap4Agent::ParseResponseCode(LPSTR lpszResponseCode)
  2802. {
  2803. HRESULT hrResult;
  2804. WORD wHashValue;
  2805. // Check arguments
  2806. Assert(m_lRefCount > 0);
  2807. Assert(NULL != lpszResponseCode);
  2808. hrResult = IXP_S_IMAP_UNRECOGNIZED_RESP;
  2809. switch (*lpszResponseCode) {
  2810. int iResult;
  2811. case 'A':
  2812. case 'a': // Possibly the "ALERT" response code
  2813. iResult = StrCmpNI(lpszResponseCode, "ALERT] ", 7);
  2814. if (0 == iResult) {
  2815. IMAP_RESPONSE irIMAPResponse;
  2816. // Definitely the "ALERT" response code:
  2817. irIMAPResponse.wParam = 0;
  2818. irIMAPResponse.lParam = 0;
  2819. irIMAPResponse.hrResult = S_OK;
  2820. irIMAPResponse.lpszResponseText = lpszResponseCode + 7;
  2821. irIMAPResponse.irtResponseType = irtSERVER_ALERT;
  2822. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  2823. hrResult = S_OK;
  2824. break;
  2825. }
  2826. // *** FALL THROUGH *** to default case
  2827. case 'P':
  2828. case 'p': // Possibly the "PARSE" or "PERMANENTFLAGS" response code
  2829. iResult = StrCmpNI(lpszResponseCode, "PERMANENTFLAGS ", 15);
  2830. if (0 == iResult) {
  2831. IMAP_MSGFLAGS PermaFlags;
  2832. LPSTR p;
  2833. DWORD dwNumBytesRead;
  2834. // Definitely the "PERMANENTFLAGS" response code:
  2835. // Parse flag list
  2836. p = lpszResponseCode + 15; // p now points to start of flag list
  2837. hrResult = ParseMsgFlagList(p, &PermaFlags, &dwNumBytesRead);
  2838. if (SUCCEEDED(hrResult)) {
  2839. IMAP_RESPONSE irIMAPResponse;
  2840. IIMAPCallback *pCBHandler;
  2841. // Record response text
  2842. p += dwNumBytesRead + 3; // p now points to response text
  2843. StrCpyN(m_szLastResponseText, p, ARRAYSIZE(m_szLastResponseText));
  2844. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  2845. &pCBHandler, irPERMANENTFLAGS_RESPONSECODE);
  2846. irIMAPResponse.hrResult = S_OK;
  2847. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  2848. irIMAPResponse.irtResponseType = irtPERMANENT_FLAGS;
  2849. irIMAPResponse.irdResponseData.imfImapMessageFlags = PermaFlags;
  2850. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  2851. }
  2852. break;
  2853. } // end of PERMANENTFLAGS response code
  2854. iResult = StrCmpNI(lpszResponseCode, "PARSE] ", 7);
  2855. if (0 == iResult) {
  2856. IMAP_RESPONSE irIMAPResponse;
  2857. // Definitely the "PARSE" response code:
  2858. irIMAPResponse.wParam = 0;
  2859. irIMAPResponse.lParam = 0;
  2860. irIMAPResponse.hrResult = S_OK;
  2861. irIMAPResponse.lpszResponseText = lpszResponseCode + 7;
  2862. irIMAPResponse.irtResponseType = irtPARSE_ERROR;
  2863. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  2864. hrResult = S_OK;
  2865. break;
  2866. } // end of PARSE response code
  2867. // *** FALL THROUGH *** to default case
  2868. case 'R':
  2869. case 'r': // Possibly "READ-ONLY" or "READ-WRITE" response
  2870. iResult = StrCmpNI(lpszResponseCode, "READ-WRITE] ", 12);
  2871. if (0 == iResult) {
  2872. IMAP_RESPONSE irIMAPResponse;
  2873. IIMAPCallback *pCBHandler;
  2874. // Definitely the "READ-WRITE" response code:
  2875. hrResult = S_OK;
  2876. // Record this for enforcement purposes
  2877. m_bCurrentMboxReadOnly = FALSE;
  2878. // Record response text for future reference
  2879. StrCpyN(m_szLastResponseText, lpszResponseCode + 12, ARRAYSIZE(m_szLastResponseText));
  2880. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  2881. &pCBHandler, irREADWRITE_RESPONSECODE);
  2882. irIMAPResponse.hrResult = S_OK;
  2883. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  2884. irIMAPResponse.irtResponseType = irtREADWRITE_STATUS;
  2885. irIMAPResponse.irdResponseData.bReadWrite = TRUE;
  2886. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  2887. break;
  2888. } // end of READ-WRITE response
  2889. iResult = StrCmpNI(lpszResponseCode, "READ-ONLY] ", 11);
  2890. if (0 == iResult) {
  2891. IMAP_RESPONSE irIMAPResponse;
  2892. IIMAPCallback *pCBHandler;
  2893. // Definitely the "READ-ONLY" response code:
  2894. hrResult = S_OK;
  2895. // Record this for enforcement purposes
  2896. m_bCurrentMboxReadOnly = TRUE;
  2897. // Record response text for future reference
  2898. StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText));
  2899. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  2900. &pCBHandler, irREADONLY_RESPONSECODE);
  2901. irIMAPResponse.hrResult = S_OK;
  2902. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  2903. irIMAPResponse.irtResponseType = irtREADWRITE_STATUS;
  2904. irIMAPResponse.irdResponseData.bReadWrite = FALSE;
  2905. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  2906. break;
  2907. } // end of READ-ONLY response
  2908. // *** FALL THROUGH *** to default case
  2909. case 'T':
  2910. case 't': // Possibly the "TRYCREATE" response
  2911. iResult = StrCmpNI(lpszResponseCode, "TRYCREATE] ", 11);
  2912. if (0 == iResult) {
  2913. IMAP_RESPONSE irIMAPResponse;
  2914. IIMAPCallback *pCBHandler;
  2915. // Definitely the "TRYCREATE" response code:
  2916. hrResult = S_OK;
  2917. StrCpyN(m_szLastResponseText, lpszResponseCode + 11, ARRAYSIZE(m_szLastResponseText));
  2918. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  2919. &pCBHandler, irTRYCREATE_RESPONSECODE);
  2920. irIMAPResponse.hrResult = S_OK;
  2921. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  2922. irIMAPResponse.irtResponseType = irtTRYCREATE;
  2923. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  2924. break;
  2925. }
  2926. // *** FALL THROUGH *** to default case
  2927. case 'U':
  2928. case 'u': // Possibly the "UIDVALIDITY" or "UNSEEN" response codes
  2929. iResult = StrCmpNI(lpszResponseCode, "UIDVALIDITY ", 12);
  2930. if (0 == iResult) {
  2931. LPSTR p, lpszEndOfNumber;
  2932. IMAP_RESPONSE irIMAPResponse;
  2933. IIMAPCallback *pCBHandler;
  2934. // Definitely the "UIDVALIDITY" response code:
  2935. hrResult = S_OK;
  2936. // Return value to our caller so they can determine sync issues
  2937. p = lpszResponseCode + 12; // p points to UID number
  2938. lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket
  2939. if (NULL == lpszEndOfNumber) {
  2940. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  2941. break;
  2942. }
  2943. *lpszEndOfNumber = '\0'; // Null-terminate the number
  2944. AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?");
  2945. StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText));
  2946. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  2947. &pCBHandler, irUIDVALIDITY_RESPONSECODE);
  2948. irIMAPResponse.hrResult = S_OK;
  2949. irIMAPResponse.lpszResponseText = m_szLastResponseText;
  2950. irIMAPResponse.irtResponseType = irtUIDVALIDITY;
  2951. irIMAPResponse.irdResponseData.dwUIDValidity = StrToUint(p);
  2952. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  2953. break;
  2954. } // end of UIDVALIDITY response code
  2955. iResult = StrCmpNI(lpszResponseCode, "UNSEEN ", 7);
  2956. if (0 == iResult) {
  2957. LPSTR p, lpszEndOfNumber;
  2958. IMAP_RESPONSE irIMAPResponse;
  2959. MBOX_MSGCOUNT mcMsgCount;
  2960. // Definitely the "UNSEEN" response code:
  2961. hrResult = S_OK;
  2962. // Record the returned number for reference during new mail DL
  2963. p = lpszResponseCode + 7; // p now points to first unseen msg num
  2964. lpszEndOfNumber = StrChr(p, ']'); // Find closing bracket
  2965. if (NULL == lpszEndOfNumber) {
  2966. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  2967. break;
  2968. }
  2969. *lpszEndOfNumber = '\0'; // Null-terminate the number
  2970. // Store response code for notification after command completion
  2971. mcMsgCount = mcMsgCount_INIT;
  2972. mcMsgCount.dwUnseen = StrToUint(p);
  2973. mcMsgCount.bGotUnseenResponse = TRUE;
  2974. irIMAPResponse.wParam = 0;
  2975. irIMAPResponse.lParam = 0;
  2976. irIMAPResponse.hrResult = S_OK;
  2977. irIMAPResponse.lpszResponseText = NULL; // Not relevant here
  2978. irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
  2979. irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
  2980. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  2981. AssertSz(cSPACE == *(lpszEndOfNumber+1), "Flakey Server?");
  2982. StrCpyN(m_szLastResponseText, lpszEndOfNumber + 2, ARRAYSIZE(m_szLastResponseText));
  2983. break;
  2984. } // end of UNSEEN response code
  2985. // *** FALL THROUGH *** to default case
  2986. default:
  2987. StrCpyN(m_szLastResponseText, lpszResponseCode, ARRAYSIZE(m_szLastResponseText));
  2988. break; // Default case: response code not recognized
  2989. } // switch(*lpszResponseCode)
  2990. return hrResult;
  2991. } // ParseResponseCode
  2992. //***************************************************************************
  2993. // Function: ParseSvrMboxResponse
  2994. //
  2995. // Purpose:
  2996. // This function parses and acts on Server and Mailbox Status Responses
  2997. // from the IMAP server (see section 7.2 of RFC-1730) (eg, CAPABILITY and
  2998. // SEARCH responses).
  2999. //
  3000. // Arguments:
  3001. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the IMAP line
  3002. // fragment to parse. It is used to retrieve the literals sent with the
  3003. // response. This pointer is updated so that it always points to the last
  3004. // processed fragment. The caller need only free the last fragment. All
  3005. // other fragments will already be freed when this function returns.
  3006. // LPSTR lpszSvrMboxResponseLine [in] - a pointer to the svr/mbox response
  3007. // line, omitting the first part of the line which identifies the response
  3008. // as tagged ("0001 ") or untagged ("* ").
  3009. // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
  3010. // if we recognized it. Otherwise does not write a value out. The caller
  3011. // must initialize this variable to irNONE before calling this function.
  3012. //
  3013. // Returns:
  3014. // HRESULT indicating success or failure. If the response is not recognized,
  3015. // this function returns IXP_E_IMAP_UNRECOGNIZED_RESP.
  3016. //***************************************************************************
  3017. HRESULT CImap4Agent::ParseSvrMboxResponse (IMAP_LINE_FRAGMENT **ppilfLine,
  3018. LPSTR lpszSvrMboxResponseLine,
  3019. IMAP_RESPONSE_ID *pirParseResult)
  3020. {
  3021. LPSTR pszTok;
  3022. HRESULT hrResult;
  3023. // Check arguments
  3024. Assert(m_lRefCount > 0);
  3025. Assert(NULL != ppilfLine);
  3026. Assert(NULL != *ppilfLine);
  3027. Assert(NULL != lpszSvrMboxResponseLine);
  3028. Assert(NULL != pirParseResult);
  3029. Assert(irNONE == *pirParseResult);
  3030. hrResult = S_OK;
  3031. // We can ID all svr/mbox status responses by looking at second char
  3032. // First, determine that the line is at least 1 character long
  3033. if ('\0' == *lpszSvrMboxResponseLine)
  3034. return IXP_E_IMAP_UNRECOGNIZED_RESP; // It's not a svr/mbox response
  3035. switch (*(lpszSvrMboxResponseLine+1)) {
  3036. int iResult;
  3037. case 'a':
  3038. case 'A': // Possibly the "CAPABILITY" response
  3039. iResult = StrCmpNI(lpszSvrMboxResponseLine, "CAPABILITY ", 11);
  3040. if (0 == iResult) {
  3041. LPSTR p;
  3042. // Definitely a "CAPABILITY" response
  3043. *pirParseResult = irCAPABILITY_RESPONSE;
  3044. // Search for and record known capabilities, discard unknowns
  3045. p = lpszSvrMboxResponseLine + 11; // p points to first cap. token
  3046. pszTok = p;
  3047. p = StrTokEx(&pszTok, g_szSpace); // p now points to next token
  3048. while (NULL != p) {
  3049. parseCapability(p); // Record capabilities which we recognize
  3050. p = StrTokEx(&pszTok, g_szSpace); // Grab next capability token
  3051. }
  3052. } // if(0 == iResult)
  3053. break; // case 'A' for possible "CAPABILITY"
  3054. case 'i':
  3055. case 'I': // Possibly the "LIST" response:
  3056. iResult = StrCmpNI(lpszSvrMboxResponseLine, "LIST ", 5);
  3057. if (0 == iResult) {
  3058. // Definitely a "LIST" response
  3059. *pirParseResult = irLIST_RESPONSE;
  3060. hrResult = ParseListLsubResponse(ppilfLine,
  3061. lpszSvrMboxResponseLine + 5, irLIST_RESPONSE);
  3062. } // if (0 == iResult)
  3063. break; // case 'I' for possible "LIST"
  3064. case 's':
  3065. case 'S': // Possibly the "LSUB" response:
  3066. iResult = StrCmpNI(lpszSvrMboxResponseLine, "LSUB ", 5);
  3067. if (0 == iResult) {
  3068. // Definitely a "LSUB" response:
  3069. *pirParseResult = irLSUB_RESPONSE;
  3070. hrResult = ParseListLsubResponse(ppilfLine,
  3071. lpszSvrMboxResponseLine + 5, irLSUB_RESPONSE);
  3072. } // if (0 == iResult)
  3073. break; // case 'S' for possible "LSUB"
  3074. case 'e':
  3075. case 'E': // Possibly the "SEARCH" response:
  3076. iResult = StrCmpNI(lpszSvrMboxResponseLine, "SEARCH", 6);
  3077. if (0 == iResult) {
  3078. // Definitely a "SEARCH" response:
  3079. *pirParseResult = irSEARCH_RESPONSE;
  3080. // Response can be "* SEARCH" or "* SEARCH <nums>". Check for null case
  3081. if (cSPACE == *(lpszSvrMboxResponseLine + 6))
  3082. hrResult = ParseSearchResponse(lpszSvrMboxResponseLine + 7);
  3083. }
  3084. break; // case 'E' for possible "SEARCH"
  3085. case 'l':
  3086. case 'L': // Possibly the "FLAGS" response:
  3087. iResult = StrCmpNI(lpszSvrMboxResponseLine, "FLAGS ", 6);
  3088. if (0 == iResult) {
  3089. IMAP_MSGFLAGS FlagsResult;
  3090. DWORD dwThrowaway;
  3091. // Definitely a "FLAGS" response:
  3092. *pirParseResult = irFLAGS_RESPONSE;
  3093. // Parse flag list
  3094. hrResult = ParseMsgFlagList(lpszSvrMboxResponseLine + 6,
  3095. &FlagsResult, &dwThrowaway);
  3096. if (SUCCEEDED(hrResult)) {
  3097. IMAP_RESPONSE irIMAPResponse;
  3098. IIMAPCallback *pCBHandler;
  3099. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  3100. &pCBHandler, irFLAGS_RESPONSE);
  3101. irIMAPResponse.hrResult = S_OK;
  3102. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  3103. irIMAPResponse.irtResponseType = irtAPPLICABLE_FLAGS;
  3104. irIMAPResponse.irdResponseData.imfImapMessageFlags = FlagsResult;
  3105. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  3106. }
  3107. } // if (0 == iResult)
  3108. break; // Case 'L' for possible "FLAGS" response
  3109. case 't':
  3110. case 'T': // Possibly the "STATUS" response:
  3111. iResult = StrCmpNI(lpszSvrMboxResponseLine, "STATUS ", 7);
  3112. if (0 == iResult) {
  3113. // Definitely a "STATUS" response
  3114. *pirParseResult = irSTATUS_RESPONSE;
  3115. hrResult = ParseMboxStatusResponse(ppilfLine,
  3116. lpszSvrMboxResponseLine + 7);
  3117. } // if (0 == iResult)
  3118. break; // Case 'T' for possible "STATUS" response
  3119. } // case(*(lpszSvrMboxResponseLine+1))
  3120. // Did we recognize the response? Return error if we didn't
  3121. if (irNONE == *pirParseResult && SUCCEEDED(hrResult))
  3122. hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
  3123. return hrResult;
  3124. } // ParseSvrMboxResponse
  3125. //***************************************************************************
  3126. // Function: ParseMsgStatusResponse
  3127. //
  3128. // Purpose:
  3129. // This function parses and acts on Message Status Responses from the IMAP
  3130. // server (see section 7.3 of RFC-1730) (eg, FETCH and EXISTS responses).
  3131. //
  3132. // Arguments:
  3133. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the IMAP line
  3134. // fragment to parse. It is used to retrieve the literals sent with the
  3135. // response. This pointer is updated so that it always points to the last
  3136. // processed fragment. The caller need only free the last fragment. All
  3137. // other fragments will already be freed when this function returns.
  3138. // LPSTR lpszMsgResponseLine [in] - pointer to response line, starting at
  3139. // the number argument.
  3140. // IMAP_RESPONSE_ID *pirParseResult [out] - identifies the IMAP response,
  3141. // if we recognized it. Otherwise does not write a value out. The caller
  3142. // must initialize this variable to irNONE before calling this function.
  3143. //
  3144. // Returns:
  3145. // HRESULT indicating success or failure. If the response is not recognized,
  3146. // this function returns IXP_E_IMAP_UNRECOGNIZED_RESP.
  3147. //***************************************************************************
  3148. HRESULT CImap4Agent::ParseMsgStatusResponse (IMAP_LINE_FRAGMENT **ppilfLine,
  3149. LPSTR lpszMsgResponseLine,
  3150. IMAP_RESPONSE_ID *pirParseResult)
  3151. {
  3152. HRESULT hrResult;
  3153. WORD wHashValue;
  3154. DWORD dwNumberArg;
  3155. LPSTR p;
  3156. // Check arguments
  3157. Assert(m_lRefCount > 0);
  3158. Assert(NULL != ppilfLine);
  3159. Assert(NULL != *ppilfLine);
  3160. Assert(NULL != lpszMsgResponseLine);
  3161. Assert(NULL != pirParseResult);
  3162. Assert(irNONE == *pirParseResult);
  3163. hrResult = S_OK;
  3164. // First, fetch the number argument
  3165. p = StrChr(lpszMsgResponseLine, cSPACE); // Find the end of the number
  3166. if (NULL == p)
  3167. return IXP_E_IMAP_SVR_SYNTAXERR;
  3168. dwNumberArg = StrToUint(lpszMsgResponseLine);
  3169. p += 1; // p now points to start of message response identifier
  3170. switch (*p) {
  3171. int iResult;
  3172. case 'E':
  3173. case 'e': // Possibly the "EXISTS" or "EXPUNGE" response
  3174. iResult = lstrcmpi(p, "EXISTS");
  3175. if (0 == iResult) {
  3176. IMAP_RESPONSE irIMAPResponse;
  3177. MBOX_MSGCOUNT mcMsgCount;
  3178. // Definitely the "EXISTS" response:
  3179. *pirParseResult = irEXISTS_RESPONSE;
  3180. // Record mailbox size for notification at completion of command
  3181. mcMsgCount = mcMsgCount_INIT;
  3182. mcMsgCount.dwExists = dwNumberArg;
  3183. mcMsgCount.bGotExistsResponse = TRUE;
  3184. irIMAPResponse.wParam = 0;
  3185. irIMAPResponse.lParam = 0;
  3186. irIMAPResponse.hrResult = S_OK;
  3187. irIMAPResponse.lpszResponseText = NULL; // Not relevant here
  3188. irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
  3189. irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
  3190. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  3191. break;
  3192. }
  3193. iResult = lstrcmpi(p, "EXPUNGE");
  3194. if (0 == iResult) {
  3195. IMAP_RESPONSE irIMAPResponse;
  3196. // Definitely the "EXPUNGE" response: Inform caller via callback
  3197. *pirParseResult = irEXPUNGE_RESPONSE;
  3198. irIMAPResponse.wParam = 0;
  3199. irIMAPResponse.lParam = 0;
  3200. irIMAPResponse.hrResult = S_OK;
  3201. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  3202. irIMAPResponse.irtResponseType = irtDELETED_MSG;
  3203. irIMAPResponse.irdResponseData.dwDeletedMsgSeqNum = dwNumberArg;
  3204. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  3205. break;
  3206. }
  3207. break; // Case 'E' or 'e' for possible "EXISTS" or "EXPUNGE" response
  3208. case 'R':
  3209. case 'r': // Possibly the "RECENT" response
  3210. iResult = lstrcmpi(p, "RECENT");
  3211. if (0 == iResult) {
  3212. IMAP_RESPONSE irIMAPResponse;
  3213. MBOX_MSGCOUNT mcMsgCount;
  3214. // Definitely the "RECENT" response:
  3215. *pirParseResult = irRECENT_RESPONSE;
  3216. // Record number for future reference
  3217. mcMsgCount = mcMsgCount_INIT;
  3218. mcMsgCount.dwRecent = dwNumberArg;
  3219. mcMsgCount.bGotRecentResponse = TRUE;
  3220. irIMAPResponse.wParam = 0;
  3221. irIMAPResponse.lParam = 0;
  3222. irIMAPResponse.hrResult = S_OK;
  3223. irIMAPResponse.lpszResponseText = NULL; // Not relevant here
  3224. irIMAPResponse.irtResponseType = irtMAILBOX_UPDATE;
  3225. irIMAPResponse.irdResponseData.pmcMsgCount = &mcMsgCount;
  3226. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  3227. }
  3228. break; // Case 'R' or 'r' for possible "RECENT" response
  3229. case 'F':
  3230. case 'f': // Possibly the "FETCH" response
  3231. iResult = StrCmpNI(p, "FETCH ", 6);
  3232. if (0 == iResult) {
  3233. // Definitely the "FETCH" response
  3234. *pirParseResult = irFETCH_RESPONSE;
  3235. p += 6;
  3236. hrResult = ParseFetchResponse(ppilfLine, dwNumberArg, p);
  3237. } // if (0 == iResult)
  3238. break; // Case 'F' or 'f' for possible "FETCH" response
  3239. } // switch(*p)
  3240. // Did we recognize the response? Return error if we didn't
  3241. if (irNONE == *pirParseResult && SUCCEEDED(hrResult))
  3242. hrResult = IXP_E_IMAP_UNRECOGNIZED_RESP;
  3243. return hrResult;
  3244. } // ParseMsgStatusResponse
  3245. //***************************************************************************
  3246. // Function: ParseListLsubResponse
  3247. //
  3248. // Purpose:
  3249. // This function parses LIST and LSUB responses and invokes the
  3250. // ListLsubResponseNotification() callback to inform the user.
  3251. //
  3252. // Arguments:
  3253. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
  3254. // IMAP response fragment. This is used to retrieve the next fragment
  3255. // in the chain (literal or line) since literals may be sent with LIST
  3256. // responses. This pointer is always updated to point to the fragment
  3257. // currently in use, so that the caller may free the last one himself.
  3258. // LPSTR lpszListResponse [in] - actually can be LIST or LSUB, but I don't
  3259. // want to have to type "ListLsub" all the time. This points into the
  3260. // middle of the LIST/LSUB response, where the mailbox_list begins (see
  3261. // RFC1730, Formal Syntax). In other words, the caller should skip past
  3262. // the initial "* LIST " or "* LSUB ", and so this ptr should point to
  3263. // a "(".
  3264. // IMAP_RESPONSE_ID irListLsubID [in] - either irLIST_RESPONSE or
  3265. // irLSUB_RESPONSE. This information is required so that we can retrieve
  3266. // the transaction ID associated with the response.
  3267. //
  3268. // Returns:
  3269. // HRESULT indicating success or failure.
  3270. //***************************************************************************
  3271. HRESULT CImap4Agent::ParseListLsubResponse(IMAP_LINE_FRAGMENT **ppilfLine,
  3272. LPSTR lpszListResponse,
  3273. IMAP_RESPONSE_ID irListLsubID)
  3274. {
  3275. LPSTR p, lpszClosingParenthesis, pszTok;
  3276. HRESULT hrResult = S_OK;
  3277. HRESULT hrTranslateResult = E_FAIL;
  3278. IMAP_MBOXFLAGS MboxFlags;
  3279. char cHierarchyChar;
  3280. IMAP_RESPONSE irIMAPResponse;
  3281. IIMAPCallback *pCBHandler;
  3282. IMAP_LISTLSUB_RESPONSE *pillrd;
  3283. LPSTR pszDecodedMboxName = NULL;
  3284. LPSTR pszMailboxName = NULL;
  3285. Assert(m_lRefCount > 0);
  3286. Assert(NULL != ppilfLine);
  3287. Assert(NULL != *ppilfLine);
  3288. Assert(NULL != lpszListResponse);
  3289. Assert(irLIST_RESPONSE == irListLsubID ||
  3290. irLSUB_RESPONSE == irListLsubID);
  3291. // We received an untagged LIST/LSUB response
  3292. // lpszListResponse = <flag list> <hierarchy char> <mailbox name>
  3293. if ('(' != *lpszListResponse)
  3294. return IXP_E_IMAP_SVR_SYNTAXERR; // We expect an opening parenthesis
  3295. p = lpszListResponse + 1; // p now points to start of first flag token
  3296. // Find position of closing parenthesis. I don't like the
  3297. // lack of efficiency, but I can fix this later. Assert(FALSE) (placeholder)
  3298. lpszClosingParenthesis = StrChr(p, ')');
  3299. if (NULL == lpszClosingParenthesis)
  3300. return IXP_E_IMAP_SVR_SYNTAXERR; // We expect a closing parenthesis
  3301. // Now process each mailbox flag returned by LIST/LSUB
  3302. *lpszClosingParenthesis = '\0'; // Null-terminate flag list
  3303. MboxFlags = IMAP_MBOX_NOFLAGS;
  3304. pszTok = p;
  3305. p = StrTokEx(&pszTok, g_szSpace); // Null-terminate first flag token
  3306. while (NULL != p) {
  3307. MboxFlags |= ParseMboxFlag(p);
  3308. p = StrTokEx(&pszTok, g_szSpace); // Grab next flag token
  3309. }
  3310. // Next, grab the hierarchy character, and advance p
  3311. // Server either sends (1) "<quoted char>" or (2) NIL
  3312. p = lpszClosingParenthesis + 1; // p now points past flag list
  3313. if (cSPACE == *p) {
  3314. LPSTR pszHC = NULL;
  3315. DWORD dwLengthOfHC;
  3316. p += 1; // p now points to start of hierarchy char spec
  3317. hrResult = NStringToString(ppilfLine, &pszHC, &dwLengthOfHC, &p);
  3318. if (FAILED(hrResult))
  3319. return hrResult;
  3320. if (hrIMAP_S_NIL_NSTRING == hrResult)
  3321. cHierarchyChar = '\0'; // Got a "NIL" for hierarchy char
  3322. else if (hrIMAP_S_QUOTED == hrResult) {
  3323. if (1 != dwLengthOfHC)
  3324. return IXP_E_IMAP_SVR_SYNTAXERR; // We should only exactly ONE char back!
  3325. else
  3326. cHierarchyChar = pszHC[0];
  3327. }
  3328. else {
  3329. // It's a literal, or something else unexpected
  3330. MemFree(pszHC);
  3331. return IXP_E_IMAP_SVR_SYNTAXERR;
  3332. }
  3333. MemFree(pszHC);
  3334. // p now points past the closing quote (thanks to NStringToString)
  3335. }
  3336. else
  3337. return IXP_E_IMAP_SVR_SYNTAXERR;
  3338. if (cSPACE != *p)
  3339. return IXP_E_IMAP_SVR_SYNTAXERR;
  3340. // Grab the mailbox name - assume size of lpszListResponse is
  3341. // whatever p has already uncovered. We expect nothing past
  3342. // this point, so we should be safe.
  3343. p += 1;
  3344. hrResult = AStringToString(ppilfLine, &pszMailboxName, NULL, &p);
  3345. if (FAILED(hrResult))
  3346. return hrResult;
  3347. // Convert the mailbox name from UTF7 to MultiByte and remember the result
  3348. hrTranslateResult = _ModifiedUTF7ToMultiByte(pszMailboxName, &pszDecodedMboxName);
  3349. if (FAILED(hrTranslateResult)) {
  3350. hrResult = hrTranslateResult;
  3351. goto error;
  3352. }
  3353. // Make sure the command line is finished (debug only)
  3354. Assert('\0' == *p);
  3355. // Notify the caller of our findings
  3356. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  3357. &pCBHandler, irListLsubID);
  3358. irIMAPResponse.hrResult = hrTranslateResult; // Could be IXP_S_IMAP_VERBATIM_MBOX
  3359. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  3360. irIMAPResponse.irtResponseType = irtMAILBOX_LISTING;
  3361. pillrd = &irIMAPResponse.irdResponseData.illrdMailboxListing;
  3362. pillrd->pszMailboxName = pszDecodedMboxName;
  3363. pillrd->imfMboxFlags = MboxFlags;
  3364. pillrd->cHierarchyChar = cHierarchyChar;
  3365. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  3366. error:
  3367. if (NULL != pszDecodedMboxName)
  3368. MemFree(pszDecodedMboxName);
  3369. if (NULL != pszMailboxName)
  3370. MemFree(pszMailboxName);
  3371. return hrResult;
  3372. } // ParseListLsubResponse
  3373. //***************************************************************************
  3374. // Function: ParseMboxFlag
  3375. //
  3376. // Purpose:
  3377. // Given a mailbox_list flag (see RFC1730, Formal Syntax), this function
  3378. // returns the IMAP_MBOX_* value which corresponds to that mailbox flag.
  3379. // For instance, given the string, "\Noinferiors", this function returns
  3380. // IMAP_MBOX_NOINFERIORS.
  3381. //
  3382. // Arguments:
  3383. // LPSTR lpszFlagToken [in] - a null-terminated string representing a
  3384. // mailbox_list flag.
  3385. //
  3386. // Returns:
  3387. // IMAP_MBOXFLAGS value. If flag is unrecognized, IMAP_MBOX_NOFLAGS is
  3388. // returned.
  3389. //***************************************************************************
  3390. IMAP_MBOXFLAGS CImap4Agent::ParseMboxFlag(LPSTR lpszFlagToken)
  3391. {
  3392. Assert(m_lRefCount > 0);
  3393. Assert(NULL != lpszFlagToken);
  3394. // We can identify the mailbox flags we know about by looking at the
  3395. // fourth character of the flag name. $REVIEW: you don't have to check
  3396. // the initial backslash, during lstrcmpi call in switch statement
  3397. // First, check that there are at least three characters
  3398. if ('\\' != *lpszFlagToken ||
  3399. '\0' == *(lpszFlagToken + 1) ||
  3400. '\0' == *(lpszFlagToken + 2))
  3401. return IMAP_MBOX_NOFLAGS;
  3402. switch (*(lpszFlagToken + 3)) {
  3403. int iResult;
  3404. case 'R':
  3405. case 'r': // Possible "\Marked" flag
  3406. iResult = lstrcmpi(lpszFlagToken, "\\Marked");
  3407. if (0 == iResult)
  3408. return IMAP_MBOX_MARKED; // Definitely the \Marked flag
  3409. break; // case 'r': // Possible "\Marked" flag
  3410. case 'I':
  3411. case 'i': // Possible "\Noinferiors" flag
  3412. iResult = lstrcmpi(lpszFlagToken, "\\Noinferiors");
  3413. if (0 == iResult)
  3414. return IMAP_MBOX_NOINFERIORS; // Definitely the \Noinferiors flag
  3415. break; // case 'i': // Possible "\Noinferiors" flag
  3416. case 'S':
  3417. case 's': // Possible "\Noselect" flag
  3418. iResult = lstrcmpi(lpszFlagToken, "\\Noselect");
  3419. if (0 == iResult)
  3420. return IMAP_MBOX_NOSELECT; // Definitely the \Noselect flag
  3421. break; // case 's': // Possible "\Noselect" flag
  3422. case 'M':
  3423. case 'm': // Possible "\Unmarked" flag
  3424. iResult = lstrcmpi(lpszFlagToken, "\\Unmarked");
  3425. if (0 == iResult)
  3426. return IMAP_MBOX_UNMARKED;
  3427. break; // case 'm': // Possible "\Unmarked" flag
  3428. } // switch (*(lpszFlagToken + 3))
  3429. return IMAP_MBOX_NOFLAGS;
  3430. } // ParseMboxFlag
  3431. //***************************************************************************
  3432. // Function: ParseFetchResponse
  3433. //
  3434. // Purpose:
  3435. // This function parses FETCH responses and calls the
  3436. // UpdateMsgNotification() callback to inform the user.
  3437. //
  3438. // Arguments:
  3439. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
  3440. // IMAP response fragment. This is used to retrieve the next fragment
  3441. // in the chain (literal or line) since literals may be sent with FETCH
  3442. // responses. This pointer is always updated to point to the fragment
  3443. // currently in use, so that the caller may free the last one himself.
  3444. // DWORD dwMsgSeqNum [in] - message sequence number of this fetch resp.
  3445. // LPSTR lpszFetchResp [in] - a pointer to the portion of the fetch
  3446. // response after "<num> FETCH " (the msg_att portion of a message_data
  3447. // item. See RFC1730 formal syntax).
  3448. //
  3449. // Returns:
  3450. // HRESULT indicating success or failure.
  3451. //***************************************************************************
  3452. HRESULT CImap4Agent::ParseFetchResponse (IMAP_LINE_FRAGMENT **ppilfLine,
  3453. DWORD dwMsgSeqNum, LPSTR lpszFetchResp)
  3454. {
  3455. LPSTR p;
  3456. FETCH_CMD_RESULTS_EX fetchResults;
  3457. FETCH_CMD_RESULTS fcrOldFetchStruct;
  3458. IMAP_RESPONSE irIMAPResponse;
  3459. HRESULT hrResult;
  3460. Assert(m_lRefCount > 0);
  3461. Assert(NULL != ppilfLine);
  3462. Assert(NULL != *ppilfLine);
  3463. Assert(0 != dwMsgSeqNum);
  3464. Assert(NULL != lpszFetchResp);
  3465. // Initialize variables
  3466. ZeroMemory(&fetchResults, sizeof(fetchResults));
  3467. p = lpszFetchResp;
  3468. if ('(' != *p) {
  3469. hrResult = IXP_E_IMAP_SVR_SYNTAXERR; // We expect opening parenthesis
  3470. goto exit;
  3471. }
  3472. // Parse each FETCH response tag (eg, RFC822, FLAGS, etc.)
  3473. hrResult = S_OK;
  3474. do {
  3475. // We'll identify FETCH tags based on the first character of tag
  3476. p += 1; // Advance p to first char
  3477. switch (*p) {
  3478. int iResult;
  3479. case 'b':
  3480. case 'B':
  3481. case 'r':
  3482. case 'R':
  3483. iResult = StrCmpNI(p, "RFC822.SIZE ", 12);
  3484. if (0 == iResult) {
  3485. // Definitely the RFC822.SIZE tag:
  3486. // Read the nstring into a stream
  3487. p += 12; // Advance p to point to number
  3488. fetchResults.bRFC822Size = TRUE;
  3489. fetchResults.dwRFC822Size = StrToUint(p);
  3490. // Advance p to point past number
  3491. while ('0' <= *p && '9' >= *p)
  3492. p += 1;
  3493. break; // case 'r' or 'R': Possible RFC822.SIZE tag
  3494. } // if (0 == iResult) for RFC822.HEADER
  3495. if (0 == StrCmpNI(p, "RFC822", 6) || 0 == StrCmpNI(p, "BODY[", 5)) {
  3496. LPSTR pszBodyTag;
  3497. LPSTR pszBody;
  3498. DWORD dwLengthOfBody;
  3499. IMAP_LINE_FRAGMENT *pilfBodyTag = NULL; // Line fragment containing the body tag
  3500. // Find the body tag. We null-terminate all body tags after first space
  3501. pszBodyTag = p;
  3502. p = StrChr(p + 6, cSPACE);
  3503. if (NULL == p) {
  3504. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  3505. goto exit;
  3506. }
  3507. *p = '\0'; // Null-terminate the body tag
  3508. p += 1; // Advance p to point to nstring
  3509. // Check if this is BODY[HEADER.FIELDS: this is the only tag that can
  3510. // include spaces and literals. We must skip past all of these.
  3511. if (0 == lstrcmpi("BODY[HEADER.FIELDS", pszBodyTag)) {
  3512. // Advance p until we find a ']'
  3513. while ('\0' != *p && ']' != *p) {
  3514. p += 1;
  3515. // Check for end of this string buffer
  3516. if ('\0' == *p) {
  3517. if (NULL == pilfBodyTag)
  3518. pilfBodyTag = *ppilfLine; // Retain for future reference
  3519. // Advance to next fragment, discarding any literals that we find
  3520. do {
  3521. if (NULL == (*ppilfLine)->pilfNextFragment) {
  3522. // No more runway! Couldn't find ']'. Free all data and bail
  3523. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  3524. while (NULL != pilfBodyTag && pilfBodyTag != *ppilfLine) {
  3525. IMAP_LINE_FRAGMENT *pilfDead;
  3526. pilfDead = pilfBodyTag;
  3527. pilfBodyTag = pilfBodyTag->pilfNextFragment;
  3528. FreeFragment(&pilfDead);
  3529. }
  3530. goto exit;
  3531. }
  3532. else
  3533. *ppilfLine = (*ppilfLine)->pilfNextFragment;
  3534. } while (iltLINE != (*ppilfLine)->iltFragmentType);
  3535. p = (*ppilfLine)->data.pszSource;
  3536. }
  3537. }
  3538. // Terminate HEADER.FIELDS chain but keep it around because we may need pszBodyTag
  3539. if (NULL != pilfBodyTag && NULL != (*ppilfLine)->pilfPrevFragment)
  3540. (*ppilfLine)->pilfPrevFragment->pilfNextFragment = NULL;
  3541. Assert(']' == *p);
  3542. Assert(cSPACE == *(p+1));
  3543. p += 2; // This should point us to the body nstring
  3544. }
  3545. // Read the nstring into a string
  3546. hrResult = NStringToString(ppilfLine, &pszBody, &dwLengthOfBody, &p);
  3547. if (FAILED(hrResult))
  3548. goto exit;
  3549. // If literal, it's already been handled. If NIL or string, report it to user
  3550. if (hrIMAP_S_QUOTED == hrResult || hrIMAP_S_NIL_NSTRING == hrResult) {
  3551. PrepareForFetchBody(dwMsgSeqNum, dwLengthOfBody, pszBodyTag);
  3552. m_dwLiteralInProgressBytesLeft = 0; // Override this
  3553. DispatchFetchBodyPart(pszBody, dwLengthOfBody, fDONT_FREE_BODY_TAG);
  3554. Assert(irsIDLE == m_irsState);
  3555. }
  3556. // Free any chains associated with HEADER.FIELDS
  3557. while (NULL != pilfBodyTag) {
  3558. IMAP_LINE_FRAGMENT *pilfDead;
  3559. pilfDead = pilfBodyTag;
  3560. pilfBodyTag = pilfBodyTag->pilfNextFragment;
  3561. FreeFragment(&pilfDead);
  3562. }
  3563. MemFree(pszBody);
  3564. break;
  3565. } // if FETCH body tag like RFC822* or BODY[*
  3566. // If not recognized, flow through (long way) to default case
  3567. case 'u':
  3568. case 'U':
  3569. iResult = StrCmpNI(p, "UID ", 4);
  3570. if (0 == iResult) {
  3571. LPSTR lpszUID;
  3572. // Definitely the UID tag
  3573. // First, find the end of the number (and verify it)
  3574. p += 4; // p now points to start of UID
  3575. lpszUID = p;
  3576. while ('\0' != *p && *p >= '0' && *p <= '9') // $REVIEW: isDigit?
  3577. p += 1;
  3578. // OK, we found end of number, and verified number is all digits
  3579. fetchResults.bUID = TRUE;
  3580. fetchResults.dwUID = StrToUint(lpszUID);
  3581. break; // case 'u' or 'U': Possible UID tag
  3582. } // if (0 == iResult)
  3583. // If not recognized, flow through (long way) to default case
  3584. case 'f':
  3585. case 'F':
  3586. iResult = StrCmpNI(p, "FLAGS ", 6);
  3587. if (0 == iResult) {
  3588. DWORD dwNumBytesRead;
  3589. // Definitely a FLAGS response: Parse the list
  3590. p += 6;
  3591. hrResult = ParseMsgFlagList(p, &fetchResults.mfMsgFlags,
  3592. &dwNumBytesRead);
  3593. if (FAILED(hrResult))
  3594. goto exit;
  3595. fetchResults.bMsgFlags = TRUE;
  3596. p += dwNumBytesRead + 1; // Advance p past end of flag list
  3597. break; // case 'f' or 'F': Possible FLAGS tag
  3598. } // if (0 == iResult)
  3599. // If not recognized, flow through to default case
  3600. case 'i':
  3601. case 'I':
  3602. iResult = StrCmpNI(p, "INTERNALDATE ", 13);
  3603. if (0 == iResult) {
  3604. LPSTR lpszEndOfDate;
  3605. // Definitely an INTERNALDATE response: convert to FILETIME
  3606. p += 13;
  3607. if ('\"' == *p)
  3608. p += 1; // Advance past the opening double-quote
  3609. else {
  3610. AssertSz(FALSE, "Server error: date_time starts without double-quote!");
  3611. }
  3612. lpszEndOfDate = StrChr(p, '\"'); // Find closing double-quote
  3613. if (NULL == lpszEndOfDate) {
  3614. AssertSz(FALSE, "Server error: date_time ends without double-quote!");
  3615. hrResult = IXP_E_IMAP_SVR_SYNTAXERR; // Can't continue, don't know where to go from
  3616. goto exit;
  3617. }
  3618. // Null-terminate end of date, for MimeOleInetDateToFileTime's sake
  3619. *lpszEndOfDate = '\0';
  3620. hrResult = MimeOleInetDateToFileTime(p, &fetchResults.ftInternalDate);
  3621. if (FAILED(hrResult))
  3622. goto exit;
  3623. p = lpszEndOfDate + 1;
  3624. fetchResults.bInternalDate = TRUE;
  3625. break; // case 'i' or 'I': Possible INTERNALDATE tag
  3626. } // (0 == iResult)
  3627. // If not recognized, flow through to default case
  3628. case 'e':
  3629. case 'E':
  3630. iResult = StrCmpNI(p, "ENVELOPE ", 9);
  3631. if (0 == iResult) {
  3632. // Definitely an envelope: parse each field!
  3633. p += 9;
  3634. hrResult = ParseEnvelope(&fetchResults, ppilfLine, &p);
  3635. if (FAILED(hrResult))
  3636. goto exit;
  3637. fetchResults.bEnvelope = TRUE;
  3638. break;
  3639. }
  3640. // If not recognized, flow through to default case
  3641. default:
  3642. // Unrecognized FETCH tag!
  3643. // $REVIEW: We should skip past the data based on common-sense
  3644. // rules. For now, just flip out. Be sure that above rules flow
  3645. // through to here if unrecognized cmd
  3646. Assert(FALSE);
  3647. goto exit;
  3648. break; // default case
  3649. } // switch (*lpszFetchResp)
  3650. // If *p is a space, we have another FETCH tag coming
  3651. } while (cSPACE == *p);
  3652. // Check if we ended on a closing parenthesis (as we always should)
  3653. if (')' != *p) {
  3654. hrResult = IXP_E_IMAP_SVR_SYNTAXERR;
  3655. goto exit;
  3656. }
  3657. // Check that there's no stuff afterwards (debug only - retail ignores)
  3658. Assert('\0' == *(p+1));
  3659. exit:
  3660. // Finished parsing the FETCH response. Call the UPDATE callback
  3661. fetchResults.dwMsgSeqNum = dwMsgSeqNum;
  3662. // Persist the cookies from body part in progress
  3663. fetchResults.lpFetchCookie1 = m_fbpFetchBodyPartInProgress.lpFetchCookie1;
  3664. fetchResults.lpFetchCookie2 = m_fbpFetchBodyPartInProgress.lpFetchCookie2;
  3665. irIMAPResponse.wParam = 0;
  3666. irIMAPResponse.lParam = 0;
  3667. irIMAPResponse.hrResult = hrResult;
  3668. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  3669. if (IMAP_FETCHEX_ENABLE & m_dwFetchFlags)
  3670. {
  3671. irIMAPResponse.irtResponseType = irtUPDATE_MSG_EX;
  3672. irIMAPResponse.irdResponseData.pFetchResultsEx = &fetchResults;
  3673. }
  3674. else
  3675. {
  3676. DowngradeFetchResponse(&fcrOldFetchStruct, &fetchResults);
  3677. irIMAPResponse.irtResponseType = irtUPDATE_MSG;
  3678. irIMAPResponse.irdResponseData.pFetchResults = &fcrOldFetchStruct;
  3679. }
  3680. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  3681. m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT;
  3682. FreeFetchResponse(&fetchResults);
  3683. return hrResult;
  3684. } // ParseFetchResponse
  3685. //***************************************************************************
  3686. // Function: ParseSearchResponse
  3687. //
  3688. // Purpose:
  3689. // This function parses SEARCH responses and calls the
  3690. // SearchResponseNotification() callback to inform the user.
  3691. //
  3692. // Arguments:
  3693. // LPSTR lpszFetchResp [in] - a pointer to the data of the search response.
  3694. // This means that the "* SEARCH" portion should be omitted.
  3695. //
  3696. // Returns:
  3697. // HRESULT indicating success or failure.
  3698. //***************************************************************************
  3699. HRESULT CImap4Agent::ParseSearchResponse(LPSTR lpszSearchResponse)
  3700. {
  3701. LPSTR p, pszTok;
  3702. IMAP_RESPONSE irIMAPResponse;
  3703. IIMAPCallback *pCBHandler;
  3704. CRangeList *pSearchResults;
  3705. Assert(m_lRefCount > 0);
  3706. Assert(NULL != lpszSearchResponse);
  3707. // First, check for the situation where there are 0 responses
  3708. p = lpszSearchResponse;
  3709. while ('\0' != *p && ('0' > *p || '9' < *p))
  3710. p += 1; // Keep going until we hit a digit
  3711. if ('\0' == *p)
  3712. return S_OK;
  3713. // Create CRangeList object
  3714. pSearchResults = new CRangeList;
  3715. if (NULL == pSearchResults)
  3716. return E_OUTOFMEMORY;
  3717. // Parse search responses
  3718. pszTok = lpszSearchResponse;
  3719. p = StrTokEx(&pszTok, g_szSpace);
  3720. while (NULL != p) {
  3721. DWORD dw;
  3722. dw = StrToUint(p);
  3723. if (0 != dw) {
  3724. HRESULT hrResult;
  3725. hrResult = pSearchResults->AddSingleValue(dw);
  3726. Assert(SUCCEEDED(hrResult));
  3727. }
  3728. else {
  3729. // Discard unusable results
  3730. AssertSz(FALSE, "Hmm, this server is into kinky search responses.");
  3731. }
  3732. p = StrTokEx(&pszTok, g_szSpace); // p now points to next number. $REVIEW: Use Opie's fstrtok!
  3733. }
  3734. // Notify user of search response.
  3735. GetTransactionID(&irIMAPResponse.wParam, &irIMAPResponse.lParam,
  3736. &pCBHandler, irSEARCH_RESPONSE);
  3737. irIMAPResponse.hrResult = S_OK;
  3738. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  3739. irIMAPResponse.irtResponseType = irtSEARCH;
  3740. irIMAPResponse.irdResponseData.prlSearchResults = (IRangeList *) pSearchResults;
  3741. OnIMAPResponse(pCBHandler, &irIMAPResponse);
  3742. pSearchResults->Release();
  3743. return S_OK;
  3744. } // ParseSearchResponse
  3745. //***************************************************************************
  3746. // Function: ParseMboxStatusResponse
  3747. //
  3748. // Purpose:
  3749. // This function parses an untagged STATUS response and calls the default
  3750. // CB handler with an irtMAILBOX_STATUS callback.
  3751. //
  3752. // Arguments:
  3753. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
  3754. // IMAP response fragment. This is used to retrieve the next fragment
  3755. // in the chain (literal or line) since literals may be sent with STATUS
  3756. // responses. This pointer is always updated to point to the fragment
  3757. // currently in use, so that the caller may free the last one himself.
  3758. // LPSTR pszStatusResponse [in] - a pointer to the STATUS response, after
  3759. // the "<tag> STATUS " portion (should point to the mailbox parameter).
  3760. //
  3761. // Returns:
  3762. // HRESULT indicating success or failure.
  3763. //***************************************************************************
  3764. HRESULT CImap4Agent::ParseMboxStatusResponse(IMAP_LINE_FRAGMENT **ppilfLine,
  3765. LPSTR pszStatusResponse)
  3766. {
  3767. LPSTR p, pszDecodedMboxName;
  3768. LPSTR pszMailbox;
  3769. HRESULT hrTranslateResult = E_FAIL;
  3770. HRESULT hrResult;
  3771. IMAP_STATUS_RESPONSE isrResult;
  3772. IMAP_RESPONSE irIMAPResponse;
  3773. // Initialize variables
  3774. p = pszStatusResponse;
  3775. ZeroMemory(&isrResult, sizeof(isrResult));
  3776. pszDecodedMboxName = NULL;
  3777. pszMailbox = NULL;
  3778. // Get the name of the mailbox
  3779. hrResult = AStringToString(ppilfLine, &pszMailbox, NULL, &p);
  3780. if (FAILED(hrResult))
  3781. goto exit;
  3782. // Convert the mailbox name from UTF7 to MultiByte and remember the result
  3783. hrTranslateResult = _ModifiedUTF7ToMultiByte(pszMailbox, &pszDecodedMboxName);
  3784. if (FAILED(hrTranslateResult)) {
  3785. hrResult = hrTranslateResult;
  3786. goto exit;
  3787. }
  3788. // Advance to first status tag
  3789. Assert(cSPACE == *p);
  3790. p += 1;
  3791. Assert('(' == *p);
  3792. // Loop through all status attributes
  3793. while ('\0' != *p && ')' != *p) {
  3794. LPSTR pszTag, pszTagValue;
  3795. DWORD dwTagValue;
  3796. // Get pointers to tag and tag value
  3797. Assert('(' == *p || cSPACE == *p);
  3798. p += 1;
  3799. pszTag = p;
  3800. while ('\0' != *p && cSPACE != *p && ')' != *p)
  3801. p += 1;
  3802. Assert(cSPACE == *p); // We expect space, then tag value
  3803. if (cSPACE == *p) {
  3804. p += 1;
  3805. Assert(*p >= '0' && *p <= '9');
  3806. pszTagValue = p;
  3807. dwTagValue = StrToUint(p);
  3808. }
  3809. // Advance us past number to next tag in prep for next loop iteration
  3810. while ('\0' != *p && cSPACE != *p && ')' != *p)
  3811. p += 1;
  3812. switch (*pszTag) {
  3813. int iResult;
  3814. case 'm':
  3815. case 'M': // Possibly the "MESSAGES" attribute
  3816. iResult = StrCmpNI(pszTag, "MESSAGES ", 9);
  3817. if (0 == iResult) {
  3818. // Definitely the "MESSAGES" tag
  3819. isrResult.fMessages = TRUE;
  3820. isrResult.dwMessages = dwTagValue;
  3821. } // if (0 == iResult)
  3822. break; // case 'M' for possible "MESSAGES"
  3823. case 'r':
  3824. case 'R': // Possibly the "RECENT" attribute
  3825. iResult = StrCmpNI(pszTag, "RECENT ", 7);
  3826. if (0 == iResult) {
  3827. // Definitely the "RECENT" tag
  3828. isrResult.fRecent = TRUE;
  3829. isrResult.dwRecent = dwTagValue;
  3830. } // if (0 == iResult)
  3831. break; // case 'R' for possible "RECENT"
  3832. case 'u':
  3833. case 'U': // Possibly UIDNEXT, UIDVALIDITY or UNSEEN
  3834. // Check for the 3 possible tags in order of expected popularity
  3835. iResult = StrCmpNI(pszTag, "UNSEEN ", 7);
  3836. if (0 == iResult) {
  3837. // Definitely the "UNSEEN" tag
  3838. isrResult.fUnseen = TRUE;
  3839. isrResult.dwUnseen = dwTagValue;
  3840. } // if (0 == iResult)
  3841. iResult = StrCmpNI(pszTag, "UIDVALIDITY ", 12);
  3842. if (0 == iResult) {
  3843. // Definitely the "UIDVALIDITY" tag
  3844. isrResult.fUIDValidity = TRUE;
  3845. isrResult.dwUIDValidity = dwTagValue;
  3846. } // if (0 == iResult)
  3847. iResult = StrCmpNI(pszTag, "UIDNEXT ", 8);
  3848. if (0 == iResult) {
  3849. // Definitely the "UIDNEXT" tag
  3850. isrResult.fUIDNext = TRUE;
  3851. isrResult.dwUIDNext = dwTagValue;
  3852. } // if (0 == iResult)
  3853. break; // case 'U' for possible UIDNEXT, UIDVALIDITY or UNSEEN
  3854. } // switch (*p)
  3855. } // while ('\0' != *p)
  3856. Assert(')' == *p);
  3857. // Call the callback with our new-found information
  3858. isrResult.pszMailboxName = pszDecodedMboxName;
  3859. irIMAPResponse.wParam = 0;
  3860. irIMAPResponse.lParam = 0;
  3861. irIMAPResponse.hrResult = hrTranslateResult; // Could be IXP_S_IMAP_VERBATIM_MBOX
  3862. irIMAPResponse.lpszResponseText = NULL; // Not relevant here
  3863. irIMAPResponse.irtResponseType = irtMAILBOX_STATUS;
  3864. irIMAPResponse.irdResponseData.pisrStatusResponse = &isrResult;
  3865. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  3866. exit:
  3867. if (NULL != pszDecodedMboxName)
  3868. MemFree(pszDecodedMboxName);
  3869. if (NULL != pszMailbox)
  3870. MemFree(pszMailbox);
  3871. return hrResult;
  3872. } // ParseMboxStatusResponse
  3873. //***************************************************************************
  3874. // Function: ParseEnvelope
  3875. //
  3876. // Purpose:
  3877. // This function parses the ENVELOPE tag returned via a FETCH response.
  3878. //
  3879. // Arguments:
  3880. // FETCH_CMD_RESULTS_EX *pEnvResults [out] - the results of parsing the
  3881. // ENVELOPE tag are outputted to this structure. It is the caller's
  3882. // responsibility to call FreeFetchResponse when finished with the data.
  3883. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current IMAP
  3884. // response fragment. This is advanced to the next fragment in the chain
  3885. // as necessary (due to literals). On function exit, this will point
  3886. // to the new current response fragment so the caller may continue parsing
  3887. // as usual.
  3888. // LPSTR *ppCurrent [in/out] - a pointer to the first '(' after the ENVELOPE
  3889. // tag. On function exit, this pointer is updated to point past the ')'
  3890. // after the ENVELOPE tag so the caller may continue parsing as usual.
  3891. //
  3892. // Returns:
  3893. // HRESULT indicating success or failure.
  3894. //***************************************************************************
  3895. HRESULT CImap4Agent::ParseEnvelope(FETCH_CMD_RESULTS_EX *pEnvResults,
  3896. IMAP_LINE_FRAGMENT **ppilfLine,
  3897. LPSTR *ppCurrent)
  3898. {
  3899. HRESULT hrResult;
  3900. LPSTR p;
  3901. LPSTR pszTemp;
  3902. TraceCall("CImap4Agent::ParseEnvelope");
  3903. p = *ppCurrent;
  3904. if ('(' != *p)
  3905. {
  3906. hrResult = TraceResult(IXP_E_IMAP_SVR_SYNTAXERR);
  3907. goto exit;
  3908. }
  3909. // (1) Parse the envelope date (ignore error)
  3910. p += 1;
  3911. hrResult = NStringToString(ppilfLine, &pszTemp, NULL, &p);
  3912. if (FAILED(hrResult))
  3913. {
  3914. TraceResult(hrResult);
  3915. goto exit;
  3916. }
  3917. hrResult = MimeOleInetDateToFileTime(pszTemp, &pEnvResults->ftENVDate);
  3918. MemFree(pszTemp);
  3919. TraceError(hrResult); // Record but otherwise ignore error
  3920. // (2) Get the "Subject" field
  3921. Assert(cSPACE == *p);
  3922. p += 1;
  3923. hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVSubject, NULL, &p);
  3924. if (FAILED(hrResult))
  3925. {
  3926. TraceResult(hrResult);
  3927. goto exit;
  3928. }
  3929. // (3) Get the "From" field
  3930. Assert(cSPACE == *p);
  3931. p += 1;
  3932. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVFrom, ppilfLine, &p);
  3933. if (FAILED(hrResult))
  3934. {
  3935. TraceResult(hrResult);
  3936. goto exit;
  3937. }
  3938. // (4) Get the "Sender" field
  3939. Assert(cSPACE == *p);
  3940. p += 1;
  3941. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVSender, ppilfLine, &p);
  3942. if (FAILED(hrResult))
  3943. {
  3944. TraceResult(hrResult);
  3945. goto exit;
  3946. }
  3947. // (5) Get the "Reply-To" field
  3948. Assert(cSPACE == *p);
  3949. p += 1;
  3950. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVReplyTo, ppilfLine, &p);
  3951. if (FAILED(hrResult))
  3952. {
  3953. TraceResult(hrResult);
  3954. goto exit;
  3955. }
  3956. // (6) Get the "To" field
  3957. Assert(cSPACE == *p);
  3958. p += 1;
  3959. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVTo, ppilfLine, &p);
  3960. if (FAILED(hrResult))
  3961. {
  3962. TraceResult(hrResult);
  3963. goto exit;
  3964. }
  3965. // (7) Get the "Cc" field
  3966. Assert(cSPACE == *p);
  3967. p += 1;
  3968. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVCc, ppilfLine, &p);
  3969. if (FAILED(hrResult))
  3970. {
  3971. TraceResult(hrResult);
  3972. goto exit;
  3973. }
  3974. // (8) Get the "Bcc" field
  3975. Assert(cSPACE == *p);
  3976. p += 1;
  3977. hrResult = ParseIMAPAddresses(&pEnvResults->piaENVBcc, ppilfLine, &p);
  3978. if (FAILED(hrResult))
  3979. {
  3980. TraceResult(hrResult);
  3981. goto exit;
  3982. }
  3983. // (9) Get the "InReplyTo" field
  3984. Assert(cSPACE == *p);
  3985. p += 1;
  3986. hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVInReplyTo, NULL, &p);
  3987. if (FAILED(hrResult))
  3988. {
  3989. TraceResult(hrResult);
  3990. goto exit;
  3991. }
  3992. // (10) Get the "MessageID" field
  3993. Assert(cSPACE == *p);
  3994. p += 1;
  3995. hrResult = NStringToString(ppilfLine, &pEnvResults->pszENVMessageID, NULL, &p);
  3996. if (FAILED(hrResult))
  3997. {
  3998. TraceResult(hrResult);
  3999. goto exit;
  4000. }
  4001. // Read in closing parenthesis
  4002. Assert(')' == *p);
  4003. p += 1;
  4004. exit:
  4005. *ppCurrent = p;
  4006. return hrResult;
  4007. } // ParseEnvelope
  4008. //***************************************************************************
  4009. // Function: ParseIMAPAddresses
  4010. //
  4011. // Purpose:
  4012. // This function parses a LIST of "address" constructs as defined in RFC2060
  4013. // formal syntax. There is no formal syntax token for this LIST, but an example
  4014. // can be found in the "env_from" token in RFC2060's formal syntax. This
  4015. // function would be called to parse "env_from".
  4016. //
  4017. // Arguments:
  4018. // IMAPADDR **ppiaResults [out] - a pointer to a chain of IMAPADDR structures
  4019. // is returned here.
  4020. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current IMAP
  4021. // response fragment. This is advanced to the next fragment in the chain
  4022. // as necessary (due to literals). On function exit, this will point
  4023. // to the new current response fragment so the caller may continue parsing
  4024. // as usual.
  4025. // LPSTR *ppCurrent [in/out] - a pointer to the first '(' after the ENVELOPE
  4026. // tag. On function exit, this pointer is updated to point past the ')'
  4027. // after the ENVELOPE tag so the caller may continue parsing as usual.
  4028. //
  4029. // Returns:
  4030. // HRESULT indicating success or failure.
  4031. //***************************************************************************
  4032. HRESULT CImap4Agent::ParseIMAPAddresses(IMAPADDR **ppiaResults,
  4033. IMAP_LINE_FRAGMENT **ppilfLine,
  4034. LPSTR *ppCurrent)
  4035. {
  4036. HRESULT hrResult = S_OK;
  4037. BOOL fResult;
  4038. IMAPADDR *piaCurrent;
  4039. LPSTR p;
  4040. TraceCall("CImap4Agent::ParseIMAPAddresses");
  4041. // Initialize output
  4042. *ppiaResults = NULL;
  4043. p = *ppCurrent;
  4044. // ppCurrent either points to an address list, or "NIL"
  4045. if ('(' != *p)
  4046. {
  4047. int iResult;
  4048. // Check for "NIL"
  4049. iResult = StrCmpNI(p, "NIL", 3);
  4050. if (0 == iResult) {
  4051. hrResult = S_OK;
  4052. p += 3; // Skip past NIL
  4053. }
  4054. else
  4055. hrResult = TraceResult(IXP_E_IMAP_SVR_SYNTAXERR);
  4056. goto exit;
  4057. }
  4058. else
  4059. p += 1; // Skip opening parenthesis
  4060. // Loop over all addresses
  4061. piaCurrent = NULL;
  4062. while ('\0' != *p && ')' != *p) {
  4063. // Skip any whitespace
  4064. while (cSPACE == *p)
  4065. p += 1;
  4066. // Skip opening parenthesis
  4067. Assert('(' == *p);
  4068. p += 1;
  4069. // Allocate a structure to hold current address
  4070. if (NULL == piaCurrent) {
  4071. fResult = MemAlloc((void **)ppiaResults, sizeof(IMAPADDR));
  4072. piaCurrent = *ppiaResults;
  4073. }
  4074. else {
  4075. fResult = MemAlloc((void **)&piaCurrent->pNext, sizeof(IMAPADDR));
  4076. piaCurrent = piaCurrent->pNext;
  4077. }
  4078. if (FALSE == fResult)
  4079. {
  4080. hrResult = TraceResult(E_OUTOFMEMORY);
  4081. goto exit;
  4082. }
  4083. ZeroMemory(piaCurrent, sizeof(IMAPADDR));
  4084. // (1) Parse addr_name (see RFC2060)
  4085. hrResult = NStringToString(ppilfLine, &piaCurrent->pszName, NULL, &p);
  4086. if (FAILED(hrResult))
  4087. {
  4088. TraceResult(hrResult);
  4089. goto exit;
  4090. }
  4091. // (2) Parse addr_adl (see RFC2060)
  4092. Assert(cSPACE == *p);
  4093. p += 1;
  4094. hrResult = NStringToString(ppilfLine, &piaCurrent->pszADL, NULL, &p);
  4095. if (FAILED(hrResult))
  4096. {
  4097. TraceResult(hrResult);
  4098. goto exit;
  4099. }
  4100. // (3) Parse addr_mailbox (see RFC2060)
  4101. Assert(cSPACE == *p);
  4102. p += 1;
  4103. hrResult = NStringToString(ppilfLine, &piaCurrent->pszMailbox, NULL, &p);
  4104. if (FAILED(hrResult))
  4105. {
  4106. TraceResult(hrResult);
  4107. goto exit;
  4108. }
  4109. // (4) Parse addr_host (see RFC2060)
  4110. Assert(cSPACE == *p);
  4111. p += 1;
  4112. hrResult = NStringToString(ppilfLine, &piaCurrent->pszHost, NULL, &p);
  4113. if (FAILED(hrResult))
  4114. {
  4115. TraceResult(hrResult);
  4116. goto exit;
  4117. }
  4118. // Skip closing parenthesis
  4119. Assert(')' == *p);
  4120. p += 1;
  4121. } // while
  4122. // Read past closing parenthesis
  4123. Assert(')' == *p);
  4124. p += 1;
  4125. exit:
  4126. if (FAILED(hrResult))
  4127. {
  4128. FreeIMAPAddresses(*ppiaResults);
  4129. *ppiaResults = NULL;
  4130. }
  4131. *ppCurrent = p;
  4132. return hrResult;
  4133. } // ParseIMAPAddresses
  4134. //***************************************************************************
  4135. // Function: DowngradeFetchResponse
  4136. //
  4137. // Purpose:
  4138. // For IIMAPTransport users who do not enable FETCH_CMD_RESULTS_EX structures
  4139. // via IIMAPTransport2::EnableFetchEx, we have to continue to report FETCH
  4140. // results using FETCH_CMD_RESULTS. This function copies the relevant data
  4141. // from a FETCH_CMD_RESULTS_EX structure to FETCH_CMD_RESULTS. Too bad IDL
  4142. // doesn't support inheritance in structures...
  4143. //
  4144. // Arguments:
  4145. // FETCH_CMD_RESULTS *pcfrOldFetchStruct [out] - points to destination for
  4146. // data contained in pfcreNewFetchStruct.
  4147. // FETCH_CMD_RESULTS_EX *pfcreNewFetchStruct [in] - points to source data
  4148. // which is to be transferred to pfcrOldFetchStruct.
  4149. //***************************************************************************
  4150. void CImap4Agent::DowngradeFetchResponse(FETCH_CMD_RESULTS *pfcrOldFetchStruct,
  4151. FETCH_CMD_RESULTS_EX *pfcreNewFetchStruct)
  4152. {
  4153. pfcrOldFetchStruct->dwMsgSeqNum = pfcreNewFetchStruct->dwMsgSeqNum;
  4154. pfcrOldFetchStruct->bMsgFlags = pfcreNewFetchStruct->bMsgFlags;
  4155. pfcrOldFetchStruct->mfMsgFlags = pfcreNewFetchStruct->mfMsgFlags;
  4156. pfcrOldFetchStruct->bRFC822Size = pfcreNewFetchStruct->bRFC822Size;
  4157. pfcrOldFetchStruct->dwRFC822Size = pfcreNewFetchStruct->dwRFC822Size;
  4158. pfcrOldFetchStruct->bUID = pfcreNewFetchStruct->bUID;
  4159. pfcrOldFetchStruct->dwUID = pfcreNewFetchStruct->dwUID;
  4160. pfcrOldFetchStruct->bInternalDate = pfcreNewFetchStruct->bInternalDate;
  4161. pfcrOldFetchStruct->ftInternalDate = pfcreNewFetchStruct->ftInternalDate;
  4162. pfcrOldFetchStruct->lpFetchCookie1 = pfcreNewFetchStruct->lpFetchCookie1;
  4163. pfcrOldFetchStruct->lpFetchCookie2 = pfcreNewFetchStruct->lpFetchCookie2;
  4164. } // DowngradeFetchResponse
  4165. //***************************************************************************
  4166. // Function: QuotedToString
  4167. //
  4168. // Purpose:
  4169. // This function, given a "quoted" (see RFC1730, Formal Syntax), converts
  4170. // it to a regular string, that is, a character array without any escape
  4171. // characters or delimiting double quotes. For instance, the quoted,
  4172. // "\"FUNKY\"\\MAN!!!!" would be converted to "FUNKY"\MAN!!!!.
  4173. //
  4174. // Arguments:
  4175. // LPSTR *ppszDestination [out] - the translated quoted is returned as
  4176. // a regular string in this destination buffer. It is the caller's
  4177. // responsibility to MemFree this buffer when finished with it.
  4178. // LPDWORD pdwLengthOfDestination [out] - the length of *ppszDestination is
  4179. // returned here. Pass NULL if not interested.
  4180. // LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the quoted,
  4181. // including opening and closing double-quotes. The function returns
  4182. // a pointer to the end of the quoted so that the caller may continue
  4183. // parsing the response line.
  4184. //
  4185. // Returns:
  4186. // HRESULT indicating success or failure. If successful, returns
  4187. // hrIMAP_S_QUOTED.
  4188. //***************************************************************************
  4189. HRESULT CImap4Agent::QuotedToString(LPSTR *ppszDestination,
  4190. LPDWORD pdwLengthOfDestination,
  4191. LPSTR *ppCurrentSrcPos)
  4192. {
  4193. LPSTR lpszSourceBuf, lpszUnescapedSequence;
  4194. CByteStream bstmQuoted;
  4195. int iUnescapedSequenceLen;
  4196. HRESULT hrResult;
  4197. Assert(m_lRefCount > 0);
  4198. Assert(NULL != ppszDestination);
  4199. Assert(NULL != ppCurrentSrcPos);
  4200. Assert(NULL != *ppCurrentSrcPos);
  4201. lpszSourceBuf = *ppCurrentSrcPos;
  4202. if ('\"' != *lpszSourceBuf)
  4203. return IXP_E_IMAP_SVR_SYNTAXERR; // Need opening double-quote
  4204. // Walk through string, translating escape characters as we go
  4205. lpszSourceBuf += 1;
  4206. lpszUnescapedSequence = lpszSourceBuf;
  4207. while('\"' != *lpszSourceBuf && '\0' != *lpszSourceBuf) {
  4208. if ('\\' == *lpszSourceBuf) {
  4209. char cEscaped;
  4210. // Escape character found, get next character
  4211. iUnescapedSequenceLen = (int) (lpszSourceBuf - lpszUnescapedSequence);
  4212. lpszSourceBuf += 1;
  4213. switch(*lpszSourceBuf) {
  4214. case '\\':
  4215. cEscaped = '\\';
  4216. break;
  4217. case '\"':
  4218. cEscaped = '\"';
  4219. break;
  4220. default:
  4221. // (Includes case '\0':)
  4222. // This isn't a spec'ed escape char!
  4223. // Return syntax error, but consider robust course of action $REVIEW
  4224. Assert(FALSE);
  4225. return IXP_E_IMAP_SVR_SYNTAXERR;
  4226. } // switch(*lpszSourceBuf)
  4227. // First, flush unescaped sequence leading up to escape sequence
  4228. if (iUnescapedSequenceLen > 0) {
  4229. hrResult = bstmQuoted.Write(lpszUnescapedSequence,
  4230. iUnescapedSequenceLen, NULL);
  4231. if (FAILED(hrResult))
  4232. return hrResult;
  4233. }
  4234. // Append escaped character
  4235. hrResult = bstmQuoted.Write(&cEscaped, 1, NULL);
  4236. if (FAILED(hrResult))
  4237. return hrResult;
  4238. // Set us up to find next unescaped sequence
  4239. lpszUnescapedSequence = lpszSourceBuf + 1;
  4240. } // if ('\' == *lpszSourceBuf)
  4241. else if (FALSE == isTEXT_CHAR(*lpszSourceBuf))
  4242. return IXP_E_IMAP_SVR_SYNTAXERR;
  4243. lpszSourceBuf += 1;
  4244. } // while not closing quote or end of string
  4245. // Flush any remaining unescaped sequences
  4246. iUnescapedSequenceLen = (int) (lpszSourceBuf - lpszUnescapedSequence);
  4247. if (iUnescapedSequenceLen > 0) {
  4248. hrResult = bstmQuoted.Write(lpszUnescapedSequence, iUnescapedSequenceLen, NULL);
  4249. if (FAILED(hrResult))
  4250. return hrResult;
  4251. }
  4252. *ppCurrentSrcPos = lpszSourceBuf + 1; // Update user's ptr to point PAST quoted
  4253. if ('\0' == *lpszSourceBuf)
  4254. return IXP_E_IMAP_SVR_SYNTAXERR; // Quoted str ended before closing quote!
  4255. else {
  4256. hrResult = bstmQuoted.HrAcquireStringA(pdwLengthOfDestination,
  4257. ppszDestination, ACQ_DISPLACE);
  4258. if (FAILED(hrResult))
  4259. return hrResult;
  4260. else
  4261. return hrIMAP_S_QUOTED;
  4262. }
  4263. } // Convert QuotedToString
  4264. //***************************************************************************
  4265. // Function: AStringToString
  4266. //
  4267. // Purpose:
  4268. // This function, given an astring (see RFC1730, Formal Syntax), converts
  4269. // it to a regular string, that is, a character array without any escape
  4270. // characters or delimiting double quotes or literal size specifications.
  4271. // As specified in RFC1730, an astring may be expressed as an atom, a
  4272. // quoted, or a literal.
  4273. //
  4274. // Arguments:
  4275. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
  4276. // IMAP response fragment. This is used to retrieve the next fragment
  4277. // in the chain (literal or line) since astrings can be sent as literals.
  4278. // This pointer is always updated to point to the fragment currently in
  4279. // use, so that the caller may free the last one himself.
  4280. // LPSTR *ppszDestination [out] - the translated astring is returned as a
  4281. // regular string in this destination buffer. It is the caller's
  4282. // responsibility to MemFree the returned buffer when finished with it.
  4283. // LPDWORD pdwLengthOfDestination [in] - the length of *ppszDestination.
  4284. // Pass NULL if not interested.
  4285. // LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the astring,
  4286. // including opening and closing double-quotes if it's a quoted, or the
  4287. // literal size specifier (ie, {#}) if it's a literal. A pointer to the
  4288. // end of the astring is returned to the caller, so that they may
  4289. // continue parsing the response line.
  4290. //
  4291. // Returns:
  4292. // HRESULT indicating success or failure. Success codes include:
  4293. // hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
  4294. // hrIMAP_S_QUOTED - a quoted was found and copied to destination
  4295. // hrIMAP_S_ATOM - an atom was found and copied to destination
  4296. //***************************************************************************
  4297. HRESULT CImap4Agent::AStringToString(IMAP_LINE_FRAGMENT **ppilfLine,
  4298. LPSTR *ppszDestination,
  4299. LPDWORD pdwLengthOfDestination,
  4300. LPSTR *ppCurrentSrcPos)
  4301. {
  4302. LPSTR pSrc;
  4303. // Check args
  4304. Assert(m_lRefCount > 0);
  4305. Assert(NULL != ppilfLine);
  4306. Assert(NULL != *ppilfLine);
  4307. Assert(NULL != ppszDestination);
  4308. Assert(NULL != ppCurrentSrcPos);
  4309. Assert(NULL != *ppCurrentSrcPos);
  4310. // Identify astring as atom, quoted or literal
  4311. pSrc = *ppCurrentSrcPos;
  4312. switch(*pSrc) {
  4313. case '{': {
  4314. IMAP_LINE_FRAGMENT *pilfLiteral, *pilfLine;
  4315. // It's a literal
  4316. // $REVIEW: We ignore the literal size spec and anything after it. Should we?
  4317. pilfLiteral = (*ppilfLine)->pilfNextFragment;
  4318. if (NULL == pilfLiteral)
  4319. return IXP_E_IMAP_INCOMPLETE_LINE;
  4320. Assert(iltLITERAL == pilfLiteral->iltFragmentType);
  4321. if (ilsSTRING == pilfLiteral->ilsLiteralStoreType) {
  4322. if (ppszDestination)
  4323. *ppszDestination = PszDupA(pilfLiteral->data.pszSource);
  4324. if (pdwLengthOfDestination)
  4325. *pdwLengthOfDestination = lstrlen(pilfLiteral->data.pszSource);
  4326. }
  4327. else {
  4328. HRESULT hrResult;
  4329. LPSTREAM pstmSource = pilfLiteral->data.pstmSource;
  4330. // Append a null-terminator to stream
  4331. hrResult = pstmSource->Write(c_szEmpty, 1, NULL);
  4332. if (FAILED(hrResult))
  4333. return hrResult;
  4334. // Copy stream into a memory block
  4335. hrResult = HrStreamToByte(pstmSource, (LPBYTE *)ppszDestination,
  4336. pdwLengthOfDestination);
  4337. if (FAILED(hrResult))
  4338. return hrResult;
  4339. if (pdwLengthOfDestination)
  4340. *pdwLengthOfDestination -= 1; // includes null-term, so decrease by 1
  4341. }
  4342. // OK, now set up next line so caller may continue parsing the response
  4343. pilfLine = pilfLiteral->pilfNextFragment;
  4344. if (NULL == pilfLine)
  4345. return IXP_E_IMAP_INCOMPLETE_LINE;
  4346. // Update user's pointer into the source line
  4347. Assert(iltLINE == pilfLine->iltFragmentType);
  4348. *ppCurrentSrcPos = pilfLine->data.pszSource;
  4349. // Clean up and exit
  4350. FreeFragment(&pilfLiteral);
  4351. FreeFragment(ppilfLine);
  4352. *ppilfLine = pilfLine; // Update this ptr so it always points to LAST fragment
  4353. return hrIMAP_S_FOUNDLITERAL;
  4354. } // case AString == LITERAL
  4355. case '\"':
  4356. // It's a QUOTED STING, convert it to regular string
  4357. return QuotedToString(ppszDestination, pdwLengthOfDestination,
  4358. ppCurrentSrcPos);
  4359. default: {
  4360. DWORD dwLengthOfAtom;
  4361. // It's an atom: find the end of the atom
  4362. while (isATOM_CHAR(*pSrc))
  4363. pSrc += 1;
  4364. // Copy the atom into a buffer for the user
  4365. dwLengthOfAtom = (DWORD) (pSrc - *ppCurrentSrcPos);
  4366. if (ppszDestination) {
  4367. BOOL fResult;
  4368. fResult = MemAlloc((void **)ppszDestination, dwLengthOfAtom + 1);
  4369. if (FALSE == fResult)
  4370. return E_OUTOFMEMORY;
  4371. CopyMemory(*ppszDestination, *ppCurrentSrcPos, dwLengthOfAtom);
  4372. (*ppszDestination)[dwLengthOfAtom] = '\0';
  4373. }
  4374. if (pdwLengthOfDestination)
  4375. *pdwLengthOfDestination = dwLengthOfAtom;
  4376. // Update user's pointer
  4377. *ppCurrentSrcPos = pSrc;
  4378. return hrIMAP_S_ATOM;
  4379. } // case AString == ATOM
  4380. } // switch(*pSrc)
  4381. } // AStringToString
  4382. //***************************************************************************
  4383. // Function: isTEXT_CHAR
  4384. //
  4385. // Purpose:
  4386. // This function identifies characters which are TEXT_CHARs as defined in
  4387. // RFC1730's Formal Syntax section.
  4388. //
  4389. // Returns:
  4390. // This function returns TRUE if the given character fits the definition.
  4391. //***************************************************************************
  4392. inline boolean CImap4Agent::isTEXT_CHAR(char c)
  4393. {
  4394. // $REVIEW: signed/unsigned char, 8/16-bit char issues with 8th bit check
  4395. // Assert(FALSE);
  4396. if (c != (c & 0x7F) || // 7-bit
  4397. '\0' == c ||
  4398. '\r' == c ||
  4399. '\n' == c)
  4400. return FALSE;
  4401. else
  4402. return TRUE;
  4403. } // isTEXT_CHAR
  4404. //***************************************************************************
  4405. // Function: isATOM_CHAR
  4406. //
  4407. // Purpose:
  4408. // This function identifies characters which are ATOM_CHARs as defined in
  4409. // RFC1730's Formal Syntax section.
  4410. //
  4411. // Returns:
  4412. // This function returns TRUE if the given character fits the definition.
  4413. //***************************************************************************
  4414. inline boolean CImap4Agent::isATOM_CHAR(char c)
  4415. {
  4416. // $REVIEW: signed/unsigned char, 8/16-bit char issues with 8th bit check
  4417. // Assert(FALSE);
  4418. if (c != (c & 0x7F) || // 7-bit
  4419. '\0' == c || // At this point, we know it's a CHAR
  4420. '(' == c || // Explicit atom_specials char
  4421. ')' == c || // Explicit atom_specials char
  4422. '{' == c || // Explicit atom_specials char
  4423. cSPACE == c || // Explicit atom_specials char
  4424. c < 0x1f || // Check for CTL
  4425. 0x7f == c || // Check for CTL
  4426. '%' == c || // Check for list_wildcards
  4427. '*' == c || // Check for list_wildcards
  4428. '\\' == c || // Check for quoted_specials
  4429. '\"' == c) // Check for quoted_specials
  4430. return FALSE;
  4431. else
  4432. return TRUE;
  4433. } // isATOM_CHAR
  4434. //***************************************************************************
  4435. // Function: NStringToString
  4436. //
  4437. // Purpose:
  4438. // This function, given an nstring (see RFC1730, Formal Syntax), converts
  4439. // it to a regular string, that is, a character array without any escape
  4440. // characters or delimiting double quotes or literal size specifications.
  4441. // As specified in RFC1730, an nstring may be expressed as a quoted,
  4442. // a literal, or "NIL".
  4443. //
  4444. // Arguments:
  4445. // IMAP_LINE_FRAGMENT **ppilfLine [in/out] - a pointer to the current
  4446. // IMAP response fragment. This is used to retrieve the next fragment
  4447. // in the chain (literal or line) since nstrings can be sent as literals.
  4448. // This pointer is always updated to point to the fragment currently in
  4449. // use, so that the caller may free the last one himself.
  4450. // LPSTR *ppszDestination [out] - the translated nstring is returned as
  4451. // a regular string in this destination buffer. It is the caller's
  4452. // responsibility to MemFree this buffer when finished with it.
  4453. // LPDWORD pdwLengthOfDestination [out] - the length of *ppszDestination is
  4454. // returned here. Pass NULL if not interested.
  4455. // LPSTR *ppCurrentSrcPos [in/out] - this is a ptr to a ptr to the nstring,
  4456. // including opening and closing double-quotes if it's a quoted, or the
  4457. // literal size specifier (ie, {#}) if it's a literal. A pointer to the
  4458. // end of the nstring is returned to the caller, so that they may
  4459. // continue parsing the response line.
  4460. //
  4461. // Returns:
  4462. // HRESULT indicating success or failure. Success codes include:
  4463. // hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
  4464. // hrIMAP_S_QUOTED - a quoted was found and copied to destination
  4465. // hrIMAP_S_NIL_NSTRING - "NIL" was found.
  4466. //***************************************************************************
  4467. HRESULT CImap4Agent::NStringToString(IMAP_LINE_FRAGMENT **ppilfLine,
  4468. LPSTR *ppszDestination,
  4469. LPDWORD pdwLengthOfDestination,
  4470. LPSTR *ppCurrentSrcPos)
  4471. {
  4472. HRESULT hrResult;
  4473. Assert(m_lRefCount > 0);
  4474. Assert(NULL != ppilfLine);
  4475. Assert(NULL != *ppilfLine);
  4476. Assert(NULL != ppszDestination);
  4477. Assert(NULL != ppCurrentSrcPos);
  4478. Assert(NULL != *ppCurrentSrcPos);
  4479. // nstrings are almost exactly like astrings, but nstrings cannot
  4480. // have any value other than "NIL" expressed as an atom.
  4481. hrResult = AStringToString(ppilfLine, ppszDestination, pdwLengthOfDestination,
  4482. ppCurrentSrcPos);
  4483. // If AStringToString found an ATOM, the only acceptable response is "NIL"
  4484. if (hrIMAP_S_ATOM == hrResult) {
  4485. if (0 == lstrcmpi("NIL", *ppszDestination)) {
  4486. **ppszDestination = '\0'; // Blank str in case someone tries to use it
  4487. if (pdwLengthOfDestination)
  4488. *pdwLengthOfDestination = 0;
  4489. return hrIMAP_S_NIL_NSTRING;
  4490. }
  4491. else {
  4492. MemFree(*ppszDestination);
  4493. *ppszDestination = NULL;
  4494. if (pdwLengthOfDestination)
  4495. *pdwLengthOfDestination = 0;
  4496. return IXP_E_IMAP_SVR_SYNTAXERR;
  4497. }
  4498. }
  4499. else
  4500. return hrResult;
  4501. } // NStringToString
  4502. //***************************************************************************
  4503. // Function: NStringToStream
  4504. //
  4505. // Purpose:
  4506. // This function is performs exactly the same job as NStringToString, but
  4507. // places the result in a stream, instead. This function should be used when
  4508. // the caller expects potentially LARGE results.
  4509. //
  4510. // Arguments:
  4511. // Similar to NStringToString (minus string buffer output args), plus:
  4512. // LPSTREAM *ppstmResult [out] - A stream is created for the caller, and
  4513. // the translated nstring is written as a regular string to the stream
  4514. // and returned via this argument. The returned stream is not rewound
  4515. // on exit.
  4516. //
  4517. // Returns:
  4518. // HRESULT indicating success or failure. Success codes include:
  4519. // hrIMAP_S_FOUNDLITERAL - a literal was found and copied to destination
  4520. // hrIMAP_S_QUOTED - a quoted was found and copied to destination
  4521. // hrIMAP_S_NIL_NSTRING - "NIL" was found.
  4522. //***************************************************************************
  4523. HRESULT CImap4Agent::NStringToStream(IMAP_LINE_FRAGMENT **ppilfLine,
  4524. LPSTREAM *ppstmResult,
  4525. LPSTR *ppCurrentSrcPos)
  4526. {
  4527. Assert(m_lRefCount > 0);
  4528. Assert(NULL != ppilfLine);
  4529. Assert(NULL != *ppilfLine);
  4530. Assert(NULL != ppstmResult);
  4531. Assert(NULL != ppCurrentSrcPos);
  4532. Assert(NULL != *ppCurrentSrcPos);
  4533. // Check if this nstring is a literal
  4534. if ('{' == **ppCurrentSrcPos) {
  4535. IMAP_LINE_FRAGMENT *pilfLine, *pilfLiteral;
  4536. // Yup, it's a literal! Write the literal to a stream
  4537. // $REVIEW: We ignore the literal size spec and anything after it. Should we?
  4538. pilfLiteral = (*ppilfLine)->pilfNextFragment;
  4539. if (NULL == pilfLiteral)
  4540. return IXP_E_IMAP_INCOMPLETE_LINE;
  4541. Assert(iltLITERAL == pilfLiteral->iltFragmentType);
  4542. if (ilsSTRING == pilfLiteral->ilsLiteralStoreType) {
  4543. HRESULT hrStreamResult;
  4544. ULONG ulNumBytesWritten;
  4545. // Literal is stored as string. Create stream and write to it
  4546. hrStreamResult = MimeOleCreateVirtualStream(ppstmResult);
  4547. if (FAILED(hrStreamResult))
  4548. return hrStreamResult;
  4549. hrStreamResult = (*ppstmResult)->Write(pilfLiteral->data.pszSource,
  4550. pilfLiteral->dwLengthOfFragment, &ulNumBytesWritten);
  4551. if (FAILED(hrStreamResult))
  4552. return hrStreamResult;
  4553. Assert(ulNumBytesWritten == pilfLiteral->dwLengthOfFragment);
  4554. }
  4555. else {
  4556. // Literal is stored as stream. Just AddRef() and return ptr
  4557. (pilfLiteral->data.pstmSource)->AddRef();
  4558. *ppstmResult = pilfLiteral->data.pstmSource;
  4559. }
  4560. // No need to null-terminate streams
  4561. // OK, now set up next line fragment so caller may continue parsing response
  4562. pilfLine = pilfLiteral->pilfNextFragment;
  4563. if (NULL == pilfLine)
  4564. return IXP_E_IMAP_INCOMPLETE_LINE;
  4565. // Update user's pointer into the source line
  4566. Assert(iltLINE == pilfLine->iltFragmentType);
  4567. *ppCurrentSrcPos = pilfLine->data.pszSource;
  4568. // Clean up and exit
  4569. FreeFragment(&pilfLiteral);
  4570. FreeFragment(ppilfLine);
  4571. *ppilfLine = pilfLine; // Update this ptr so it always points to LAST fragment
  4572. return hrIMAP_S_FOUNDLITERAL;
  4573. }
  4574. else {
  4575. HRESULT hrResult, hrStreamResult;
  4576. ULONG ulLiteralLen, ulNumBytesWritten;
  4577. LPSTR pszLiteralSrc;
  4578. // Not a literal. Translate NString to string (in-place).
  4579. // Add 1 to destination size calculation for null-terminator
  4580. hrResult = NStringToString(ppilfLine, &pszLiteralSrc,
  4581. &ulLiteralLen, ppCurrentSrcPos);
  4582. if (FAILED(hrResult))
  4583. return hrResult;
  4584. // Create stream to hold result
  4585. hrStreamResult = MimeOleCreateVirtualStream(ppstmResult);
  4586. if (FAILED(hrStreamResult)) {
  4587. MemFree(pszLiteralSrc);
  4588. return hrStreamResult;
  4589. }
  4590. // Write the result to the stream
  4591. hrStreamResult = (*ppstmResult)->Write(pszLiteralSrc, ulLiteralLen,
  4592. &ulNumBytesWritten);
  4593. MemFree(pszLiteralSrc);
  4594. if (FAILED(hrStreamResult))
  4595. return hrStreamResult;
  4596. Assert(ulLiteralLen == ulNumBytesWritten); // Debug-only paranoia
  4597. return hrResult;
  4598. }
  4599. } // NStringToStream
  4600. //***************************************************************************
  4601. // Function: ParseMsgFlagList
  4602. //
  4603. // Purpose:
  4604. // Given a flag_list (see RFC1730, Formal Syntax section), this function
  4605. // returns the IMAP_MSG_* bit-flags which correspond to the flags in the
  4606. // list. For instance, given the flag list, "(\Answered \Draft)", this
  4607. // function returns IMAP_MSG_ANSWERED | IMAP_MSG_DRAFT. Any unrecognized
  4608. // flags are ignored.
  4609. //
  4610. // Arguments:
  4611. // LPSTR lpszStartOfFlagList [in/out] - a pointer the start of a flag_list,
  4612. // including opening and closing parentheses. This function does not
  4613. // explicitly output anything to this string, but it does MODIFY the
  4614. // string by null-terminating spaces and the closing parenthesis.
  4615. // IMAP_MSGFLAGS *lpmfMsgFlags [out] - IMAP_MSGFLAGS value corresponding
  4616. // to the given flag list. If the given flag list is empty, this
  4617. // function returns IMAP_MSG_NOFLAGS.
  4618. // LPDWORD lpdwNumBytesRead [out] - the number of bytes between the
  4619. // opening parenthesis and the closing parenthesis of the flag list.
  4620. // Adding this number to the address of the start of the flag list
  4621. // yields a pointer to the closing parenthesis.
  4622. //
  4623. // Returns:
  4624. // HRESULT indicating success or failure.
  4625. //***************************************************************************
  4626. HRESULT CImap4Agent::ParseMsgFlagList(LPSTR lpszStartOfFlagList,
  4627. IMAP_MSGFLAGS *lpmfMsgFlags,
  4628. LPDWORD lpdwNumBytesRead)
  4629. {
  4630. LPSTR p, lpszEndOfFlagList, pszTok;
  4631. Assert(m_lRefCount > 0);
  4632. Assert(NULL != lpszStartOfFlagList);
  4633. Assert(NULL != lpmfMsgFlags);
  4634. Assert(NULL != lpdwNumBytesRead);
  4635. p = lpszStartOfFlagList;
  4636. if ('(' != *p)
  4637. // Opening parenthesis was not found
  4638. return IXP_E_IMAP_SVR_SYNTAXERR;
  4639. // Look for closing parenthesis Assert(FALSE); // *** $REVIEW: C-RUNTIME ALERT
  4640. lpszEndOfFlagList = StrChr(p, ')');
  4641. if (NULL == lpszEndOfFlagList)
  4642. // Closing parenthesis was not found
  4643. return IXP_E_IMAP_SVR_SYNTAXERR;
  4644. *lpdwNumBytesRead = (DWORD) (lpszEndOfFlagList - lpszStartOfFlagList);
  4645. *lpszEndOfFlagList = '\0'; // Null-terminate flag list
  4646. *lpmfMsgFlags = IMAP_MSG_NOFLAGS; // Initialize output
  4647. pszTok = lpszStartOfFlagList + 1;
  4648. p = StrTokEx(&pszTok, g_szSpace); // Get ptr to first token
  4649. while (NULL != p) {
  4650. // We'll narrow the search down for the flag by looking at its
  4651. // first letter. Although there's a conflict between \Deleted and
  4652. // \Draft, this is the best way for case-insensitive search
  4653. // (first non-conflicting letter is five characters in!)
  4654. // First, check that there is at least one character
  4655. if ('\\' == *p) {
  4656. p += 1;
  4657. switch (*p) {
  4658. int iResult;
  4659. case 'a':
  4660. case 'A': // Possible "Answered" flag
  4661. iResult = lstrcmpi(p, c_szIMAP_MSG_ANSWERED);
  4662. if (0 == iResult)
  4663. *lpmfMsgFlags |= IMAP_MSG_ANSWERED; // Definitely the \Answered flag
  4664. break;
  4665. case 'f':
  4666. case 'F': // Possible "Flagged" flag
  4667. iResult = lstrcmpi(p, c_szIMAP_MSG_FLAGGED);
  4668. if (0 == iResult)
  4669. *lpmfMsgFlags |= IMAP_MSG_FLAGGED; // Definitely the \Flagged flag
  4670. break;
  4671. case 'd':
  4672. case 'D': // Possible "Deleted" or "Draft" flags
  4673. // "Deleted" is more probable, so check it first
  4674. iResult = lstrcmpi(p, c_szIMAP_MSG_DELETED);
  4675. if (0 == iResult) {
  4676. *lpmfMsgFlags |= IMAP_MSG_DELETED; // Definitely the \Deleted flag
  4677. break;
  4678. }
  4679. iResult = lstrcmpi(p, c_szIMAP_MSG_DRAFT);
  4680. if (0 == iResult) {
  4681. *lpmfMsgFlags |= IMAP_MSG_DRAFT; // Definitely the \Draft flag
  4682. break;
  4683. }
  4684. break;
  4685. case 's':
  4686. case 'S': // Possible "Seen" flags
  4687. iResult = lstrcmpi(p, c_szIMAP_MSG_SEEN);
  4688. if (0 == iResult)
  4689. *lpmfMsgFlags |= IMAP_MSG_SEEN; // Definitely the \Seen flag
  4690. break;
  4691. } // switch(*p)
  4692. } // if ('\\' == *p)
  4693. p = StrTokEx(&pszTok, g_szSpace); // Grab next token
  4694. } // while (NULL != p)
  4695. return S_OK; // If we hit this point, we're all done
  4696. } // ParseMsgFlagList
  4697. //****************************************************************************
  4698. // Function: AppendSendAString
  4699. //
  4700. // Purpose:
  4701. // This function is intended to be used by a caller who is constructing a
  4702. // command line which contains IMAP astrings (see RFC1730 Formal Syntax).
  4703. // This function takes a regular C string and converts it to an IMAP astring,
  4704. // appending it to the end of the command line under construction.
  4705. //
  4706. // An astring may take the form of an atom, a quoted, or a literal. For
  4707. // performance reasons (both conversion and network), I don't see any reason
  4708. // we should ever output an atom. Thus, this function returns either a quoted
  4709. // or a literal.
  4710. //
  4711. // Although IMAP's most expressive form of astring is the literal, it can
  4712. // result in costly network handshaking between client and server, and
  4713. // thus should be avoided unless required. Another consideration to use
  4714. // in deciding to use literal/quoted is size of the string. Most IMAP servers
  4715. // will have some internal limit to the maximum length of a line. To avoid
  4716. // exceeding this limit, it is wise to encode large strings as literals
  4717. // (where large typically means 1024 bytes).
  4718. //
  4719. // If the function converts the C string to a quoted, it appends it to the
  4720. // end of the partially-constructed command line. If it must send as a literal,
  4721. // it enqueues the partially-constructed command line in the send queue of the
  4722. // command-in-progress, enqueues the literal as well, then creates a new line
  4723. // fragment so the caller may continue constructing the command. The caller's
  4724. // pointer to the end of the command line is reset so that the user may
  4725. // append the next argument without concern of whether the C string
  4726. // was sent as a quoted or a literal. Although the caller may pretend
  4727. // that he's constructing a command line simply by appending to it, when this
  4728. // function returns, he caller may not be appending to the same string buffer.
  4729. // (Not that the caller should care.)
  4730. //
  4731. // This function prepends a SPACE by default, so this function may be called
  4732. // as many times in a row as desired. Each astring will be separated by a
  4733. // space.
  4734. //
  4735. // Arguments:
  4736. // CIMAPCmdInfo *piciCommand [in] - a pointer to the command currently under
  4737. // construction. This argument is needed so we can enqueue command
  4738. // fragments to the command's send queue.
  4739. // LPSTR lpszCommandLine [in] - a pointer to a partially constructed
  4740. // command line suitable for passing to SendCmdLine (which supplies the
  4741. // tag). For instance, this argument could point to a string, "SELECT".
  4742. // LPSTR *ppCmdLinePos [in/out] - a pointer to the end of the command
  4743. // line. If this function converts the C string to a quoted, the quoted
  4744. // is appended to lpszCommandLine, and *ppCmdLinePos is updated to point
  4745. // to the end of the quoted. If the C string is converted to a literal,
  4746. // lpszCommandLine is made blank (null-terminated), and *ppCmdLinePos
  4747. // is reset to the start of the line. In either case, the user should
  4748. // continue to construct the command line using the updated *ppCmdLinePos
  4749. // pointer, and send lpszCommandLine as usual to SendCmdLine.
  4750. // DWORD dwSizeOfCommandLine [in] - size of the command line buffer, for
  4751. // buffer overflow-checking purposes.
  4752. // LPSTR lpszSource [in] - pointer to the source string.
  4753. // BOOL fPrependSpace [in] - TRUE if we should prepend a space, FALSE if no
  4754. // space should be prepended. Usually TRUE unless this AString follows
  4755. // a rangelist.
  4756. //
  4757. // Returns:
  4758. // HRESULT indicating success or failure. In particular, there are two
  4759. // success codes (which the caller need not act on):
  4760. //
  4761. // hrIMAP_S_QUOTED - indicates that the source string was successfully
  4762. // converted to a quoted, and has been appended to lpszCommandLine.
  4763. // *ppCmdLinePos has been updated to point to the end of the new line
  4764. // should the caller wish to continue appending arguments.
  4765. // hrIMAP_S_FOUNDLITERAL - indicates that the source string was
  4766. // sent as a literal. The command line has been blanked, and the user
  4767. // may continue constructing the command line with his *ppCmdLinePos ptr.
  4768. //****************************************************************************
  4769. HRESULT CImap4Agent::AppendSendAString(CIMAPCmdInfo *piciCommand,
  4770. LPSTR lpszCommandLine, LPSTR *ppCmdLinePos,
  4771. DWORD dwSizeOfCommandLine, LPCSTR lpszSource,
  4772. BOOL fPrependSpace)
  4773. {
  4774. HRESULT hrResult;
  4775. DWORD dwMaxQuotedSize;
  4776. DWORD dwSizeOfQuoted;
  4777. Assert(m_lRefCount > 0);
  4778. Assert(NULL != piciCommand);
  4779. Assert(NULL != lpszCommandLine);
  4780. Assert(NULL != ppCmdLinePos);
  4781. Assert(NULL != *ppCmdLinePos);
  4782. Assert(0 != dwSizeOfCommandLine);
  4783. Assert(NULL != lpszSource);
  4784. Assert(*ppCmdLinePos < lpszCommandLine + dwSizeOfCommandLine);
  4785. // Assume quoted string at start. If we have to send as literal, then too
  4786. // bad, the quoted conversion work is wasted.
  4787. // Prepend a space if so directed by user
  4788. if (fPrependSpace) {
  4789. **ppCmdLinePos = cSPACE;
  4790. *ppCmdLinePos += 1;
  4791. }
  4792. dwMaxQuotedSize = min(dwLITERAL_THRESHOLD,
  4793. (DWORD) (lpszCommandLine + dwSizeOfCommandLine - *ppCmdLinePos));
  4794. hrResult = StringToQuoted(*ppCmdLinePos, lpszSource, dwMaxQuotedSize,
  4795. &dwSizeOfQuoted);
  4796. // Always check for buffer overflow
  4797. Assert(*ppCmdLinePos + dwSizeOfQuoted < lpszCommandLine + dwSizeOfCommandLine);
  4798. if (SUCCEEDED(hrResult)) {
  4799. Assert(hrIMAP_S_QUOTED == hrResult);
  4800. // Successfully converted to quoted,
  4801. *ppCmdLinePos += dwSizeOfQuoted; // Advance user's ptr into cmd line
  4802. }
  4803. else {
  4804. BOOL bResult;
  4805. DWORD dwLengthOfLiteral;
  4806. DWORD dwLengthOfLiteralSpec;
  4807. IMAP_LINE_FRAGMENT *pilfLiteral;
  4808. // OK, couldn't convert to quoted (buffer overflow? 8-bit char?)
  4809. // Looks like it's literal time. We SEND this puppy.
  4810. // Find out length of literal, append to command line and send
  4811. dwLengthOfLiteral = lstrlen(lpszSource); // Yuck, but I'm betting most Astrings are quoted
  4812. dwLengthOfLiteralSpec = wnsprintf(*ppCmdLinePos, dwSizeOfCommandLine - (DWORD)(*ppCmdLinePos - lpszCommandLine),
  4813. "{%lu}\r\n", dwLengthOfLiteral);
  4814. Assert(*ppCmdLinePos + dwLengthOfLiteralSpec < lpszCommandLine + dwSizeOfCommandLine);
  4815. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, lpszCommandLine,
  4816. (DWORD) (*ppCmdLinePos + dwLengthOfLiteralSpec - lpszCommandLine)); // Send entire command line
  4817. if (FAILED(hrResult))
  4818. return hrResult;
  4819. // Queue the literal up - send FSM will wait for cmd continuation
  4820. pilfLiteral = new IMAP_LINE_FRAGMENT;
  4821. pilfLiteral->iltFragmentType = iltLITERAL;
  4822. pilfLiteral->ilsLiteralStoreType = ilsSTRING;
  4823. pilfLiteral->dwLengthOfFragment = dwLengthOfLiteral;
  4824. pilfLiteral->pilfNextFragment = NULL;
  4825. pilfLiteral->pilfPrevFragment = NULL;
  4826. DWORD cchSize = (dwLengthOfLiteral + 1);
  4827. bResult = MemAlloc((void **) &pilfLiteral->data.pszSource, cchSize * sizeof(pilfLiteral->data.pszSource[0]));
  4828. if (FALSE == bResult)
  4829. {
  4830. delete pilfLiteral;
  4831. return E_OUTOFMEMORY;
  4832. }
  4833. StrCpyN(pilfLiteral->data.pszSource, lpszSource, cchSize);
  4834. EnqueueFragment(pilfLiteral, piciCommand->pilqCmdLineQueue);
  4835. // Done with sending cmd line w/ literal. Blank out old cmd line and rewind ptr
  4836. *ppCmdLinePos = lpszCommandLine;
  4837. *lpszCommandLine = '\0';
  4838. hrResult = hrIMAP_S_FOUNDLITERAL;
  4839. } // else: convert AString to Literal
  4840. return hrResult;
  4841. } // AppendSendAString
  4842. //****************************************************************************
  4843. // Function: StringToQuoted
  4844. //
  4845. // Purpose:
  4846. // This function converts a regular C string to an IMAP quoted (see RFC1730
  4847. // Formal Syntax).
  4848. //
  4849. // Arguments:
  4850. // LPSTR lpszDestination [out] - the output buffer where the quoted should
  4851. // be placed.
  4852. // LPSTR lpszSource [in] - the source string.
  4853. // DWORD dwSizeOfDestination [in] - the size of the output buffer,
  4854. // lpszDestination. Note that in the worst case, the size of the output
  4855. // buffer must be at least one character larger than the quoted actually
  4856. // needs. This is because before translating a character from source to
  4857. // destination, the loop checks if there is enough room for the worst
  4858. // case, a quoted_special, which needs 2 bytes.
  4859. // LPDWORD lpdwNumCharsWritten [out] - the number of characters written
  4860. // to the output buffer, not including the null-terminator. Adding this
  4861. // value to lpszDestination will result in a pointer to the end of the
  4862. // quoted.
  4863. //
  4864. // Returns:
  4865. // HRESULT indicating success or failure. In particular, this function
  4866. // returns hrIMAP_S_QUOTED if it was successful in converting the source
  4867. // string to a quoted. If not, the function returns E_FAIL.
  4868. //****************************************************************************
  4869. HRESULT CImap4Agent::StringToQuoted(LPSTR lpszDestination, LPCSTR lpszSource,
  4870. DWORD dwSizeOfDestination,
  4871. LPDWORD lpdwNumCharsWritten)
  4872. {
  4873. LPCSTR p;
  4874. DWORD dwNumBytesWritten;
  4875. Assert(NULL != lpszDestination);
  4876. Assert(NULL != lpszSource);
  4877. // Initialize return value
  4878. *lpdwNumCharsWritten = 0;
  4879. if (dwSizeOfDestination >= 3)
  4880. dwSizeOfDestination -= 2; // Leave room for closing quote and null-term at end
  4881. else {
  4882. Assert(FALSE); // Smallest quoted is 3 chars ('\"\"\0')
  4883. return IXP_E_IMAP_BUFFER_OVERFLOW;
  4884. }
  4885. p = lpszSource;
  4886. *lpszDestination = '\"'; // Start us off with an opening quote
  4887. lpszDestination += 1;
  4888. dwNumBytesWritten = 1;
  4889. // Keep looping until we hit source null-term, or until we don't have
  4890. // enough room in destination for largest output (2 chars for quoted_special)
  4891. dwSizeOfDestination -= 1; // This ensures always room for quoted_special
  4892. while (dwNumBytesWritten < dwSizeOfDestination && '\0' != *p) {
  4893. if (FALSE == isTEXT_CHAR(*p))
  4894. return E_FAIL; // Quoted's can only represent TEXT_CHAR's
  4895. if ('\\' == *p || '\"' == *p) {
  4896. *lpszDestination = '\\'; // Prefix with escape character
  4897. lpszDestination += 1;
  4898. dwNumBytesWritten += 1;
  4899. } // if quoted_special
  4900. *lpszDestination = *p;
  4901. lpszDestination += 1;
  4902. dwNumBytesWritten += 1;
  4903. p += 1;
  4904. } // while ('\0' != *p)
  4905. *lpszDestination = '\"'; // Install closing quote
  4906. *(lpszDestination + 1) = '\0'; // Null-terminate the string
  4907. *lpdwNumCharsWritten = dwNumBytesWritten + 1; // Incl closing quote in size
  4908. if ('\0' == *p)
  4909. return hrIMAP_S_QUOTED;
  4910. else
  4911. return IXP_E_IMAP_BUFFER_OVERFLOW; // Buffer overflow
  4912. } // StringToQuoted
  4913. //***************************************************************************
  4914. // Function: GenerateCommandTag
  4915. //
  4916. // Purpose:
  4917. // This function generates a unique tag so that a command issuer may
  4918. // identify his command to the IMAP server (and so that the server response
  4919. // may be identified with the command). It is a simple base-36 (alphanumeric)
  4920. // counter which increments a static 4-digit base-36 number on each call.
  4921. // (Digits are 0,1,2,3,4,6,7,8,9,A,B,C,...,Z).
  4922. //
  4923. // Returns:
  4924. // No return value. This function always succeeds.
  4925. //***************************************************************************
  4926. void CImap4Agent::GenerateCommandTag(LPSTR lpszTag)
  4927. {
  4928. static char szCurrentTag[NUM_TAG_CHARS+1] = "ZZZZ";
  4929. LPSTR p;
  4930. boolean bWraparound;
  4931. // Check arguments
  4932. Assert(m_lRefCount > 0);
  4933. Assert(NULL != lpszTag);
  4934. EnterCriticalSection(&m_csTag);
  4935. // Increment current tag
  4936. p = szCurrentTag + NUM_TAG_CHARS - 1; // p now points to last tag character
  4937. do {
  4938. bWraparound = FALSE;
  4939. *p += 1;
  4940. // Increment from '9' should jump to 'A'
  4941. if (*p > '9' && *p < 'A')
  4942. *p = 'A';
  4943. else if (*p > 'Z') {
  4944. // Increment from 'Z' should wrap around to '0'
  4945. *p = '0';
  4946. bWraparound = TRUE;
  4947. p -= 1; // Advance pointer to more significant character
  4948. }
  4949. } while (TRUE == bWraparound && szCurrentTag <= p);
  4950. LeaveCriticalSection(&m_csTag);
  4951. // Return result to caller
  4952. StrCpyN(lpszTag, szCurrentTag, TAG_BUFSIZE);
  4953. } // GenerateCommandTag
  4954. //***************************************************************************
  4955. // Function NoArgCommand
  4956. //
  4957. // Purpose:
  4958. // This function can construct a command line for any function of the
  4959. // form: <tag> <command>.
  4960. //
  4961. // This function constructs the command line, sends it out, and returns the
  4962. // result of the send operation.
  4963. //
  4964. // Arguments:
  4965. // LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
  4966. // IMAP_COMMAND icCommandID [in] - the command ID for this command,
  4967. // eg, icCREATE_COMMAND.
  4968. // SERVERSTATE ssMinimumState [in] - minimum server state required for
  4969. // the given command. Used for debug purposes only.
  4970. // WPARAM wParam [in] - (see below)
  4971. // LPARAM lParam [in] - wParam and lParam form a unique ID assigned by the
  4972. // caller to this IMAP command and its responses. Can be anything, but
  4973. // note that the value of 0, 0 is reserved for unsolicited responses.
  4974. //
  4975. // Returns:
  4976. // HRESULT indicating success or failure.
  4977. //***************************************************************************
  4978. HRESULT CImap4Agent::NoArgCommand(LPCSTR lpszCommandVerb,
  4979. IMAP_COMMAND icCommandID,
  4980. SERVERSTATE ssMinimumState,
  4981. WPARAM wParam, LPARAM lParam,
  4982. IIMAPCallback *pCBHandler)
  4983. {
  4984. HRESULT hrResult;
  4985. char szBuffer[CMDLINE_BUFSIZE];
  4986. CIMAPCmdInfo *piciCommand;
  4987. DWORD dwCmdLineLen;
  4988. // Verify proper server state and set us up as current command
  4989. Assert(m_lRefCount > 0);
  4990. Assert(NULL != lpszCommandVerb);
  4991. Assert(icNO_COMMAND != icCommandID);
  4992. // Only accept cmds if server is in proper state, OR if we're connecting,
  4993. // and the cmd requires Authenticated state or less
  4994. if (ssMinimumState > m_ssServerState &&
  4995. (ssConnecting != m_ssServerState || ssMinimumState > ssAuthenticated)) {
  4996. // Forgive the NOOP command, due to bug #31968 (err msg build-up if svr drops conn)
  4997. AssertSz(icNOOP_COMMAND == icCommandID,
  4998. "The IMAP server is not in the correct state to accept this command.");
  4999. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5000. }
  5001. piciCommand = new CIMAPCmdInfo(this, icCommandID, ssMinimumState,
  5002. wParam, lParam, pCBHandler);
  5003. if (piciCommand == NULL) {
  5004. hrResult = E_OUTOFMEMORY;
  5005. goto error;
  5006. }
  5007. dwCmdLineLen = wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s\r\n", piciCommand->szTag, lpszCommandVerb);
  5008. // Send command to server
  5009. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, dwCmdLineLen);
  5010. if (FAILED(hrResult))
  5011. goto error;
  5012. // Transmit command and register with IMAP response parser
  5013. hrResult = SubmitIMAPCommand(piciCommand);
  5014. error:
  5015. if (FAILED(hrResult))
  5016. delete piciCommand;
  5017. return hrResult;
  5018. } // NoArgCommand
  5019. //***************************************************************************
  5020. // Function OneArgCommand
  5021. //
  5022. // Purpose:
  5023. // This function can construct a command line for any function of the
  5024. // form: <tag> <command> <astring>. This currently includes SELECT, EXAMINE,
  5025. // CREATE, DELETE, SUBSCRIBE and UNSUBSCRIBE. Since all of these commands
  5026. // require the server to be in Authorized state, I don't bother asking for
  5027. // a minimum SERVERSTATE argument.
  5028. //
  5029. // This function constructs the command line, sends it out, and returns the
  5030. // result of the send operation.
  5031. //
  5032. // Arguments:
  5033. // LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
  5034. // LPSTR lpszMboxName [in] - a C string representing the argument for the
  5035. // command. It is automatically converted to an IMAP astring.
  5036. // IMAP_COMMAND icCommandID [in] - the command ID for this command,
  5037. // eg, icCREATE_COMMAND.
  5038. // WPARAM wParam [in] - (see below)
  5039. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5040. // the caller to this IMAP command and its responses. Can be anything,
  5041. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5042. //
  5043. // Returns:
  5044. // HRESULT indicating success or failure.
  5045. //***************************************************************************
  5046. HRESULT CImap4Agent::OneArgCommand(LPCSTR lpszCommandVerb, LPSTR lpszMboxName,
  5047. IMAP_COMMAND icCommandID,
  5048. WPARAM wParam, LPARAM lParam,
  5049. IIMAPCallback *pCBHandler)
  5050. {
  5051. HRESULT hrResult;
  5052. char szBuffer[CMDLINE_BUFSIZE];
  5053. CIMAPCmdInfo *piciCommand;
  5054. LPSTR p, pszUTF7MboxName;
  5055. // Verify proper server state and set us up as current command
  5056. Assert(m_lRefCount > 0);
  5057. Assert(NULL != lpszCommandVerb);
  5058. Assert(NULL != lpszMboxName);
  5059. Assert(icNO_COMMAND != icCommandID);
  5060. // Only accept cmds if server is in proper state, OR if we're connecting,
  5061. // and the cmd requires Authenticated state or less (always TRUE in this case)
  5062. if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
  5063. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  5064. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5065. }
  5066. // Initialize variables
  5067. pszUTF7MboxName = NULL;
  5068. piciCommand = new CIMAPCmdInfo(this, icCommandID, ssAuthenticated,
  5069. wParam, lParam, pCBHandler);
  5070. // Construct command line
  5071. p = szBuffer;
  5072. p += wnsprintf(szBuffer, ARRAYSIZE(szBuffer), "%s %s", piciCommand->szTag, lpszCommandVerb);
  5073. // Convert mailbox name to UTF-7
  5074. hrResult = _MultiByteToModifiedUTF7(lpszMboxName, &pszUTF7MboxName);
  5075. if (FAILED(hrResult))
  5076. goto error;
  5077. // Don't worry about long mailbox name overflow, long mbox names will be sent as literals
  5078. hrResult = AppendSendAString(piciCommand, szBuffer, &p, sizeof(szBuffer), pszUTF7MboxName);
  5079. if (FAILED(hrResult))
  5080. goto error;
  5081. // Send command
  5082. p += wnsprintf(p, ARRAYSIZE(szBuffer) - (DWORD)(p - szBuffer), "\r\n"); // Append CRLF
  5083. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szBuffer, (DWORD) (p - szBuffer));
  5084. if (FAILED(hrResult))
  5085. goto error;
  5086. // Transmit command and register with IMAP response parser
  5087. hrResult = SubmitIMAPCommand(piciCommand);
  5088. error:
  5089. if (FAILED(hrResult))
  5090. delete piciCommand;
  5091. if (NULL != pszUTF7MboxName)
  5092. MemFree(pszUTF7MboxName);
  5093. return hrResult;
  5094. } // OneArgCommand
  5095. //***************************************************************************
  5096. // Function TwoArgCommand
  5097. //
  5098. // Purpose:
  5099. // This function can construct a command line for any function of the
  5100. // form: <tag> <command> <astring> <astring>.
  5101. //
  5102. // This function constructs the command line, sends it out, and returns the
  5103. // result of the send operation.
  5104. //
  5105. // Arguments:
  5106. // LPCSTR lpszCommandVerb [in] - the command verb, eg, "CREATE".
  5107. // LPCSTR lpszFirstArg [in] - a C string representing the first argument for
  5108. // the command. It is automatically converted to an IMAP astring.
  5109. // LPCSTR lpszSecondArg [in] - a C string representing the first argument
  5110. // for the command. It is automatically converted to an IMAP astring.
  5111. // IMAP_COMMAND icCommandID [in] - the command ID for this command,
  5112. // eg, icCREATE_COMMAND.
  5113. // SERVERSTATE ssMinimumState [in] - minimum server state required for
  5114. // the given command. Used for debug purposes only.
  5115. // WPARAM wParam [in] - (see below)
  5116. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5117. // the caller to this IMAP command and its responses. Can be anything,
  5118. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5119. //
  5120. // Returns:
  5121. // HRESULT indicating success or failure.
  5122. //***************************************************************************
  5123. HRESULT CImap4Agent::TwoArgCommand(LPCSTR lpszCommandVerb,
  5124. LPCSTR lpszFirstArg,
  5125. LPCSTR lpszSecondArg,
  5126. IMAP_COMMAND icCommandID,
  5127. SERVERSTATE ssMinimumState,
  5128. WPARAM wParam, LPARAM lParam,
  5129. IIMAPCallback *pCBHandler)
  5130. {
  5131. HRESULT hrResult;
  5132. CIMAPCmdInfo *piciCommand;
  5133. char szCommandLine[CMDLINE_BUFSIZE];
  5134. LPSTR p;
  5135. // Verify proper server state and set us up as current command
  5136. Assert(m_lRefCount > 0);
  5137. Assert(NULL != lpszCommandVerb);
  5138. Assert(NULL != lpszFirstArg);
  5139. Assert(NULL != lpszSecondArg);
  5140. Assert(icNO_COMMAND != icCommandID);
  5141. // Only accept cmds if server is in proper state, OR if we're connecting,
  5142. // and the cmd requires Authenticated state or less
  5143. if (ssMinimumState > m_ssServerState &&
  5144. (ssConnecting != m_ssServerState || ssMinimumState > ssAuthenticated)) {
  5145. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  5146. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5147. }
  5148. piciCommand = new CIMAPCmdInfo(this, icCommandID, ssMinimumState,
  5149. wParam, lParam, pCBHandler);
  5150. if (piciCommand == NULL) {
  5151. hrResult = E_OUTOFMEMORY;
  5152. goto error;
  5153. }
  5154. // Send command to server, wait for response
  5155. p = szCommandLine;
  5156. p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s %s", piciCommand->szTag, lpszCommandVerb);
  5157. // Don't worry about buffer overflow, long strings will be sent as literals
  5158. hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
  5159. sizeof(szCommandLine), lpszFirstArg);
  5160. if (FAILED(hrResult))
  5161. goto error;
  5162. // Don't worry about buffer overflow, long strings will be sent as literals
  5163. hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
  5164. sizeof(szCommandLine), lpszSecondArg);
  5165. if (FAILED(hrResult))
  5166. goto error;
  5167. p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), "\r\n"); // Append CRLF
  5168. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
  5169. if (FAILED(hrResult))
  5170. goto error;
  5171. // Transmit command and register with IMAP response parser
  5172. hrResult = SubmitIMAPCommand(piciCommand);
  5173. error:
  5174. if (FAILED(hrResult))
  5175. delete piciCommand;
  5176. return hrResult;
  5177. } // TwoArgCommand
  5178. //***************************************************************************
  5179. // Function: RangedCommand
  5180. //
  5181. // Purpose:
  5182. // This function can construct a command line for any function of the
  5183. // form: <tag> <command> <msg range> <string>.
  5184. //
  5185. // This function constructs the command line, sends it out, and returns the
  5186. // result of the send operation. It is the caller's responsiblity to construct
  5187. // a string with proper IMAP syntax.
  5188. //
  5189. // Arguments:
  5190. // LPCSTR lpszCommandVerb [in] - the command verb, eg, "SEARCH".
  5191. // boolean bUIDPrefix [in] - TRUE if the command verb should be prefixed with
  5192. // UID, as in the case of "UID SEARCH".
  5193. // IRangeList *pMsgRange [in] - the message range for this command. The
  5194. // caller can pass NULL for this argument to omit the range, but ONLY
  5195. // if the pMsgRange represents a UID message range.
  5196. // boolean bUIDRangeList [in] - TRUE if pMsgRange represents a UID range,
  5197. // FALSE if pMsgRange represents a message sequence number range. Ignored
  5198. // if pMsgRange is NULL.
  5199. // boolean bAStringCmdArgs [in] - TRUE if lpszCmdArgs should be sent as
  5200. // an ASTRING, FALSE if lpszCmdArgs should be sent
  5201. // LPCSTR lpszCmdArgs [in] - a C string representing the remaining argument
  5202. // for the command. It is the caller's responsibility to ensure that
  5203. // this string is proper IMAP syntax.
  5204. // IMAP_COMMAND icCommandID [in] - the command ID for this command,
  5205. // eg, icSEARCH_COMMAND.
  5206. // WPARAM wParam [in] - (see below)
  5207. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5208. // the caller to this IMAP command and its responses. Can be anything,
  5209. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5210. //
  5211. // Returns:
  5212. // HRESULT indicating success or failure.
  5213. //***************************************************************************
  5214. HRESULT CImap4Agent::RangedCommand(LPCSTR lpszCommandVerb,
  5215. boolean bUIDPrefix,
  5216. IRangeList *pMsgRange,
  5217. boolean bUIDRangeList,
  5218. boolean bAStringCmdArgs,
  5219. LPSTR lpszCmdArgs,
  5220. IMAP_COMMAND icCommandID,
  5221. WPARAM wParam, LPARAM lParam,
  5222. IIMAPCallback *pCBHandler)
  5223. {
  5224. HRESULT hrResult;
  5225. CIMAPCmdInfo *piciCommand;
  5226. char szCommandLine[CMDLINE_BUFSIZE];
  5227. DWORD dwCmdLineLen;
  5228. BOOL fAStringPrependSpace = TRUE;
  5229. // Verify proper server state and set us up as current command
  5230. Assert(m_lRefCount > 0);
  5231. Assert(NULL != lpszCommandVerb);
  5232. Assert(NULL != lpszCmdArgs);
  5233. AssertSz(NULL != pMsgRange || TRUE == bUIDRangeList ||
  5234. icSEARCH_COMMAND == icCommandID, "Only UID cmds or SEARCH can omit msg range");
  5235. Assert(icNO_COMMAND != icCommandID);
  5236. // All ranged commands require selected state
  5237. // Only accept cmds if server is in proper state, OR if we're connecting,
  5238. // and the cmd requires Authenticated state or less (always FALSE in this case)
  5239. if (ssSelected > m_ssServerState) {
  5240. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  5241. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5242. }
  5243. piciCommand = new CIMAPCmdInfo(this, icCommandID, ssSelected,
  5244. wParam, lParam, pCBHandler);
  5245. if (NULL == piciCommand) {
  5246. hrResult = E_OUTOFMEMORY;
  5247. goto error;
  5248. }
  5249. piciCommand->fUIDRangeList = bUIDRangeList;
  5250. // Construct command tag and verb, append to command-line queue
  5251. dwCmdLineLen = wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine),
  5252. bUIDPrefix ? "%s UID %s " : "%s %s ",
  5253. piciCommand->szTag, lpszCommandVerb);
  5254. // Special case: if SEARCH command, UID rangelist requires "UID" in front of range
  5255. if (icSEARCH_COMMAND == icCommandID && NULL != pMsgRange && bUIDRangeList)
  5256. dwCmdLineLen += wnsprintf(szCommandLine + dwCmdLineLen, (ARRAYSIZE(szCommandLine) - dwCmdLineLen), "UID ");
  5257. if (NULL != pMsgRange) {
  5258. Assert(dwCmdLineLen + 1 < sizeof(szCommandLine)); // Check for overflow
  5259. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, dwCmdLineLen);
  5260. if (FAILED(hrResult))
  5261. goto error;
  5262. // Add message range to command-line queue, if it exists
  5263. hrResult = SendRangelist(piciCommand, pMsgRange, bUIDRangeList);
  5264. if (FAILED(hrResult))
  5265. goto error;
  5266. pMsgRange = NULL; // If we get to this point, MemFree of rangelist will be handled
  5267. fAStringPrependSpace = FALSE; // Rangelist automatically APPENDS space
  5268. dwCmdLineLen = 0;
  5269. }
  5270. // Now append the command-line arguments (remember to append CRLF)
  5271. if (bAStringCmdArgs) {
  5272. LPSTR p;
  5273. p = szCommandLine + dwCmdLineLen;
  5274. // Don't worry about long mailbox name overflow, long mbox names will be sent as literals
  5275. hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
  5276. sizeof(szCommandLine), lpszCmdArgs, fAStringPrependSpace);
  5277. if (FAILED(hrResult))
  5278. goto error;
  5279. p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), "\r\n"); // Append CRLF
  5280. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END,
  5281. szCommandLine, (DWORD) (p - szCommandLine));
  5282. if (FAILED(hrResult))
  5283. goto error;
  5284. }
  5285. else {
  5286. if (dwCmdLineLen > 0) {
  5287. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, dwCmdLineLen);
  5288. if (FAILED(hrResult))
  5289. goto error;
  5290. }
  5291. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END | sclAPPEND_CRLF,
  5292. lpszCmdArgs, lstrlen(lpszCmdArgs));
  5293. if (FAILED(hrResult))
  5294. goto error;
  5295. }
  5296. // Transmit command and register with IMAP response parser
  5297. hrResult = SubmitIMAPCommand(piciCommand);
  5298. error:
  5299. if (FAILED(hrResult)) {
  5300. if (NULL != piciCommand)
  5301. delete piciCommand;
  5302. }
  5303. return hrResult;
  5304. } // RangedCommand
  5305. //***************************************************************************
  5306. // Function TwoMailboxCommand
  5307. //
  5308. // Purpose:
  5309. // This function is a wrapper function for TwoArgCommand. This function
  5310. // converts the two mailbox names to modified IMAP UTF-7 before submitting
  5311. // the two arguments to TwoArgCommand.
  5312. //
  5313. // Arguments:
  5314. // Same as for TwoArgCommandm except for name/const change:
  5315. // LPSTR lpszFirstMbox [in] - pointer to the first mailbox argument.
  5316. // LPSTR lpszSecondMbox [in] - pointer to the second mailbox argument.
  5317. //
  5318. // Returns:
  5319. // Same as TwoArgCommand.
  5320. //***************************************************************************
  5321. HRESULT CImap4Agent::TwoMailboxCommand(LPCSTR lpszCommandVerb,
  5322. LPSTR lpszFirstMbox,
  5323. LPSTR lpszSecondMbox,
  5324. IMAP_COMMAND icCommandID,
  5325. SERVERSTATE ssMinimumState,
  5326. WPARAM wParam, LPARAM lParam,
  5327. IIMAPCallback *pCBHandler)
  5328. {
  5329. LPSTR pszUTF7FirstMbox, pszUTF7SecondMbox;
  5330. HRESULT hrResult;
  5331. // Initialize variables
  5332. pszUTF7FirstMbox = NULL;
  5333. pszUTF7SecondMbox = NULL;
  5334. // Convert both mailbox names to UTF-7
  5335. hrResult = _MultiByteToModifiedUTF7(lpszFirstMbox, &pszUTF7FirstMbox);
  5336. if (FAILED(hrResult))
  5337. goto exit;
  5338. hrResult = _MultiByteToModifiedUTF7(lpszSecondMbox, &pszUTF7SecondMbox);
  5339. if (FAILED(hrResult))
  5340. goto exit;
  5341. hrResult = TwoArgCommand(lpszCommandVerb, pszUTF7FirstMbox, pszUTF7SecondMbox,
  5342. icCommandID, ssMinimumState, wParam, lParam, pCBHandler);
  5343. exit:
  5344. if (NULL != pszUTF7FirstMbox)
  5345. MemFree(pszUTF7FirstMbox);
  5346. if (NULL != pszUTF7SecondMbox)
  5347. MemFree(pszUTF7SecondMbox);
  5348. return hrResult;
  5349. } // TwoMailboxCommand
  5350. //***************************************************************************
  5351. // Function: parseCapability
  5352. //
  5353. // Purpose:
  5354. // The CAPABILITY response from the IMAP server consists of a list of
  5355. // capbilities, with each capability names separated by a space. This
  5356. // function takes a capability name (null-terminated) as its argument.
  5357. // If the name is recognized, we set the appropriate flags in
  5358. // m_dwCapabilityFlags. Otherwise, we do nothing.
  5359. //
  5360. //
  5361. // Returns:
  5362. // No return value. This function always succeeds.
  5363. //***************************************************************************
  5364. void CImap4Agent::parseCapability (LPSTR lpszCapabilityToken)
  5365. {
  5366. DWORD dwCapabilityFlag;
  5367. LPSTR p;
  5368. int iResult;
  5369. Assert(m_lRefCount > 0);
  5370. p = lpszCapabilityToken;
  5371. dwCapabilityFlag = 0;
  5372. switch (*lpszCapabilityToken) {
  5373. case 'I':
  5374. case 'i': // Possible IMAP4, IMAP4rev1
  5375. iResult = lstrcmpi(p, "IMAP4");
  5376. if (0 == iResult) {
  5377. dwCapabilityFlag = IMAP_CAPABILITY_IMAP4;
  5378. break;
  5379. }
  5380. iResult = lstrcmpi(p, "IMAP4rev1");
  5381. if (0 == iResult) {
  5382. dwCapabilityFlag = IMAP_CAPABILITY_IMAP4rev1;
  5383. break;
  5384. }
  5385. iResult = lstrcmpi(p, "IDLE");
  5386. if (0 == iResult) {
  5387. dwCapabilityFlag = IMAP_CAPABILITY_IDLE;
  5388. break;
  5389. }
  5390. break; // case 'I' for possible IMAP4, IMAP4rev1, IDLE
  5391. case 'A':
  5392. case 'a': // Possible AUTH
  5393. if (0 == StrCmpNI(p, "AUTH-", 5) ||
  5394. 0 == StrCmpNI(p, "AUTH=", 5)) {
  5395. // Parse the authentication mechanism after the '-' or '='
  5396. // I don't recognize any, at the moment
  5397. p += 5;
  5398. AddAuthMechanism(p);
  5399. }
  5400. break; // case 'A' for possible AUTH
  5401. default:
  5402. break; // Do nothing
  5403. } // switch (*lpszCapabilityToken)
  5404. m_dwCapabilityFlags |= dwCapabilityFlag;
  5405. } // parseCapability
  5406. //***************************************************************************
  5407. // Function: AddAuthMechanism
  5408. //
  5409. // Purpose:
  5410. // This function adds an authentication token from the server (returned via
  5411. // CAPABILITY) to our internal list of authentication mechanisms supported
  5412. // by the server.
  5413. //
  5414. // Arguments:
  5415. // LPSTR pszAuthMechanism [in] - a pointer to a null-terminated
  5416. // authentication token from the server returned via CAPABILITY. For
  5417. // instance, "KERBEROS_V4" is an example of an auth token.
  5418. //***************************************************************************
  5419. void CImap4Agent::AddAuthMechanism(LPSTR pszAuthMechanism)
  5420. {
  5421. AssertSz(NULL == m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens],
  5422. "Memory's a-leaking, and you've just lost an authentication mechanism.");
  5423. if (NULL == pszAuthMechanism || '\0' == *pszAuthMechanism) {
  5424. AssertSz(FALSE, "No authentication mechanism, here!");
  5425. return;
  5426. }
  5427. if (m_asAuthStatus.iNumAuthTokens >= MAX_AUTH_TOKENS) {
  5428. AssertSz(FALSE, "No room in array for more auth tokens!");
  5429. return;
  5430. }
  5431. m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens] =
  5432. PszDupA(pszAuthMechanism);
  5433. Assert(NULL != m_asAuthStatus.rgpszAuthTokens[m_asAuthStatus.iNumAuthTokens]);
  5434. m_asAuthStatus.iNumAuthTokens += 1;
  5435. } // AddAuthMechanism
  5436. //***************************************************************************
  5437. // Function: Capability
  5438. //
  5439. // Purpose:
  5440. // The CImap4Agent class always asks for the server's CAPABILITIES after
  5441. // a connection is established. The result is saved in a register and
  5442. // is available by calling this function.
  5443. //
  5444. // Arguments:
  5445. // DWORD *pdwCapabilityFlags [out] - a DWORD with bit-flags specifying
  5446. // which capabilities this IMAP server supports is returned here.
  5447. //
  5448. // Returns:
  5449. // HRESULT indicating success or failure.
  5450. //***************************************************************************
  5451. HRESULT STDMETHODCALLTYPE CImap4Agent::Capability (DWORD *pdwCapabilityFlags)
  5452. {
  5453. Assert(m_lRefCount > 0);
  5454. Assert(NULL != pdwCapabilityFlags);
  5455. if (m_ssServerState < ssNonAuthenticated) {
  5456. AssertSz(FALSE, "Must be connected before I can return capabilities");
  5457. *pdwCapabilityFlags = 0;
  5458. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5459. }
  5460. else {
  5461. *pdwCapabilityFlags = m_dwCapabilityFlags;
  5462. return S_OK;
  5463. }
  5464. } // Capability
  5465. //***************************************************************************
  5466. // Function: Select
  5467. //
  5468. // Purpose:
  5469. // This function issues a SELECT command to the IMAP server.
  5470. //
  5471. // Arguments:
  5472. // WPARAM wParam [in] - (see below)
  5473. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5474. // the caller to this IMAP command and its responses. Can be anything,
  5475. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5476. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5477. // responses for this command. If this is NULL, the default CB handler
  5478. // is used.
  5479. // LPSTR lpszMailboxName - pointer to IMAP-compliant mailbox name
  5480. //
  5481. // Returns:
  5482. // HRESULT indicating success or failure of send operation.
  5483. //***************************************************************************
  5484. HRESULT STDMETHODCALLTYPE CImap4Agent::Select(WPARAM wParam, LPARAM lParam,
  5485. IIMAPCallback *pCBHandler,
  5486. LPSTR lpszMailboxName)
  5487. {
  5488. return OneArgCommand("SELECT", lpszMailboxName, icSELECT_COMMAND,
  5489. wParam, lParam, pCBHandler);
  5490. // Successful SELECT bumps server status to ssSelected
  5491. } // Select
  5492. //***************************************************************************
  5493. // Function: Examine
  5494. //
  5495. // Purpose:
  5496. // This function issues an EXAMINE command to the IMAP server.
  5497. //
  5498. // Arguments:
  5499. // Same as for the Select() function.
  5500. //
  5501. // Returns:
  5502. // Same as for the Select() function.
  5503. //***************************************************************************
  5504. HRESULT STDMETHODCALLTYPE CImap4Agent::Examine(WPARAM wParam, LPARAM lParam,
  5505. IIMAPCallback *pCBHandler,
  5506. LPSTR lpszMailboxName)
  5507. {
  5508. return OneArgCommand("EXAMINE", lpszMailboxName, icEXAMINE_COMMAND,
  5509. wParam, lParam, pCBHandler);
  5510. // Successful EXAMINE bumps server status to ssSelected
  5511. } // Examine
  5512. //***************************************************************************
  5513. // Function: Create
  5514. //
  5515. // Purpose:
  5516. // This function issues a CREATE command to the IMAP server.
  5517. //
  5518. // Arguments:
  5519. // WPARAM wParam [in] - (see below)
  5520. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5521. // the caller to this IMAP command and its responses. Can be anything,
  5522. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5523. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5524. // responses for this command. If this is NULL, the default CB handler
  5525. // is used.
  5526. // LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
  5527. //
  5528. // Returns:
  5529. // HRESULT indicating success or failure of send operation.
  5530. //***************************************************************************
  5531. HRESULT STDMETHODCALLTYPE CImap4Agent::Create(WPARAM wParam, LPARAM lParam,
  5532. IIMAPCallback *pCBHandler,
  5533. LPSTR lpszMailboxName)
  5534. {
  5535. return OneArgCommand("CREATE", lpszMailboxName, icCREATE_COMMAND,
  5536. wParam, lParam, pCBHandler);
  5537. } // Create
  5538. //***************************************************************************
  5539. // Function: Delete
  5540. //
  5541. // Purpose:
  5542. // This function issues a DELETE command to the IMAP server.
  5543. //
  5544. // Arguments:
  5545. // WPARAM wParam [in] - (see below)
  5546. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5547. // the caller to this IMAP command and its responses. Can be anything,
  5548. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5549. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5550. // responses for this command. If this is NULL, the default CB handler
  5551. // is used.
  5552. // LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
  5553. //
  5554. // Returns:
  5555. // HRESULT indicating success or failure of send operation.
  5556. //***************************************************************************
  5557. HRESULT STDMETHODCALLTYPE CImap4Agent::Delete(WPARAM wParam, LPARAM lParam,
  5558. IIMAPCallback *pCBHandler,
  5559. LPSTR lpszMailboxName)
  5560. {
  5561. return OneArgCommand("DELETE", lpszMailboxName, icDELETE_COMMAND,
  5562. wParam, lParam, pCBHandler);
  5563. } // Delete
  5564. //***************************************************************************
  5565. // Function: Rename
  5566. //
  5567. // Purpose:
  5568. // This function issues a RENAME command to the IMAP server.
  5569. //
  5570. // Arguments:
  5571. // WPARAM wParam [in] - (see below)
  5572. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5573. // the caller to this IMAP command and its responses. Can be anything,
  5574. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5575. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5576. // responses for this command. If this is NULL, the default CB handler
  5577. // is used.
  5578. // LPSTR lpszMailboxName - CURRENT IMAP-compliant name of the mailbox.
  5579. // LPSTR lpszNewMailboxName - NEW IMAP-compliant name of the mailbox.
  5580. //
  5581. // Returns:
  5582. // HRESULT indicating success or failure of send operation.
  5583. //***************************************************************************
  5584. HRESULT STDMETHODCALLTYPE CImap4Agent::Rename(WPARAM wParam, LPARAM lParam,
  5585. IIMAPCallback *pCBHandler,
  5586. LPSTR lpszMailboxName,
  5587. LPSTR lpszNewMailboxName)
  5588. {
  5589. return TwoMailboxCommand("RENAME", lpszMailboxName, lpszNewMailboxName,
  5590. icRENAME_COMMAND, ssAuthenticated, wParam, lParam, pCBHandler);
  5591. } // Rename
  5592. //***************************************************************************
  5593. // Function: Subscribe
  5594. //
  5595. // Purpose:
  5596. // This function issues a SUBSCRIBE command to the IMAP server.
  5597. //
  5598. // Arguments:
  5599. // WPARAM wParam [in] - (see below)
  5600. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5601. // the caller to this IMAP command and its responses. Can be anything,
  5602. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5603. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5604. // responses for this command. If this is NULL, the default CB handler
  5605. // is used.
  5606. // LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
  5607. //
  5608. // Returns:
  5609. // HRESULT indicating success or failure of send operation.
  5610. //***************************************************************************
  5611. HRESULT STDMETHODCALLTYPE CImap4Agent::Subscribe(WPARAM wParam, LPARAM lParam,
  5612. IIMAPCallback *pCBHandler,
  5613. LPSTR lpszMailboxName)
  5614. {
  5615. return OneArgCommand("SUBSCRIBE", lpszMailboxName, icSUBSCRIBE_COMMAND,
  5616. wParam, lParam, pCBHandler);
  5617. } // Subscribe
  5618. //***************************************************************************
  5619. // Function: Unsubscribe
  5620. //
  5621. // Purpose:
  5622. // This function issues an UNSUBSCRIBE command to the IMAP server.
  5623. //
  5624. // Arguments:
  5625. // WPARAM wParam [in] - (see below)
  5626. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5627. // the caller to this IMAP command and its responses. Can be anything,
  5628. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5629. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5630. // responses for this command. If this is NULL, the default CB handler
  5631. // is used.
  5632. // LPSTR lpszMailboxName - IMAP-compliant name of the mailbox.
  5633. //
  5634. // Returns:
  5635. // HRESULT indicating success or failure of send operation.
  5636. //***************************************************************************
  5637. HRESULT STDMETHODCALLTYPE CImap4Agent::Unsubscribe(WPARAM wParam, LPARAM lParam,
  5638. IIMAPCallback *pCBHandler,
  5639. LPSTR lpszMailboxName)
  5640. {
  5641. return OneArgCommand("UNSUBSCRIBE", lpszMailboxName, icUNSUBSCRIBE_COMMAND,
  5642. wParam, lParam, pCBHandler);
  5643. } // Unsubscribe
  5644. //***************************************************************************
  5645. // Function: List
  5646. //
  5647. // Purpose:
  5648. // This function issues a LIST command to the IMAP server.
  5649. //
  5650. // Arguments:
  5651. // WPARAM wParam [in] - (see below)
  5652. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5653. // the caller to this IMAP command and its responses. Can be anything,
  5654. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5655. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5656. // responses for this command. If this is NULL, the default CB handler
  5657. // is used.
  5658. // LPSTR lpszMailboxNameReference - IMAP-compliant reference for mbox name
  5659. // LPSTR lpszMailboxNamePattern - IMAP-compliant pattern for mailbox name
  5660. //
  5661. // Returns:
  5662. // HRESULT indicating success or failure of send operation.
  5663. //***************************************************************************
  5664. HRESULT STDMETHODCALLTYPE CImap4Agent::List(WPARAM wParam, LPARAM lParam,
  5665. IIMAPCallback *pCBHandler,
  5666. LPSTR lpszMailboxNameReference,
  5667. LPSTR lpszMailboxNamePattern)
  5668. {
  5669. return TwoMailboxCommand("LIST", lpszMailboxNameReference,
  5670. lpszMailboxNamePattern, icLIST_COMMAND, ssAuthenticated, wParam,
  5671. lParam, pCBHandler);
  5672. } // List
  5673. //***************************************************************************
  5674. // Function: Lsub
  5675. //
  5676. // Purpose:
  5677. // This function issues a LSUB command to the IMAP server.
  5678. //
  5679. // Arguments:
  5680. // WPARAM wParam [in] - (see below)
  5681. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5682. // the caller to this IMAP command and its responses. Can be anything,
  5683. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5684. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5685. // responses for this command. If this is NULL, the default CB handler
  5686. // is used.
  5687. // LPSTR lpszMailboxNameReference - IMAP-compliant reference for mbox name
  5688. // LPSTR lpszMailboxNamePattern - IMAP-compliant pattern for mailbox name.
  5689. //
  5690. // Returns:
  5691. // HRESULT indicating success or failure of send operation.
  5692. //***************************************************************************
  5693. HRESULT STDMETHODCALLTYPE CImap4Agent::Lsub(WPARAM wParam, LPARAM lParam,
  5694. IIMAPCallback *pCBHandler,
  5695. LPSTR lpszMailboxNameReference,
  5696. LPSTR lpszMailboxNamePattern)
  5697. {
  5698. return TwoMailboxCommand("LSUB", lpszMailboxNameReference,
  5699. lpszMailboxNamePattern, icLSUB_COMMAND, ssAuthenticated, wParam,
  5700. lParam, pCBHandler);
  5701. } // Lsub
  5702. //***************************************************************************
  5703. // Function: Append
  5704. //
  5705. // Purpose:
  5706. // This function issues an APPEND command to the IMAP server.
  5707. //
  5708. // Arguments:
  5709. // WPARAM wParam [in] - (see below)
  5710. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5711. // the caller to this IMAP command and its responses. Can be anything,
  5712. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5713. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5714. // responses for this command. If this is NULL, the default CB handler
  5715. // is used.
  5716. // LPSTR lpszMailboxName - IMAP-compliant mailbox name to append message to.
  5717. // LPSTR lpszMessageFlags - IMAP-compliant list of msg flags to set for msg.
  5718. // Set to NULL to set no message flags. (Avoid passing "()" due to old Cyrus
  5719. // server bug). $REVIEW: This should be changed to IMAP_MSGFLAGS!!!
  5720. // FILETIME ftMessageDateTime - date/time to associate with msg (GMT/UTC)
  5721. // LPSTREAM lpstmMessageToSave - the message to save, in RFC822 format.
  5722. // No need to rewind the stream, this is done by CConnection::SendStream.
  5723. //
  5724. // Returns:
  5725. // HRESULT indicating success or failure of send operation.
  5726. //***************************************************************************
  5727. HRESULT STDMETHODCALLTYPE CImap4Agent::Append(WPARAM wParam, LPARAM lParam,
  5728. IIMAPCallback *pCBHandler,
  5729. LPSTR lpszMailboxName,
  5730. LPSTR lpszMessageFlags,
  5731. FILETIME ftMessageDateTime,
  5732. LPSTREAM lpstmMessageToSave)
  5733. {
  5734. // PARTYTIME!!
  5735. const SYSTEMTIME stDefaultDateTime = {1999, 12, 5, 31, 23, 59, 59, 999};
  5736. HRESULT hrResult;
  5737. FILETIME ftLocalTime;
  5738. SYSTEMTIME stMsgDateTime;
  5739. DWORD dwTimeZoneId;
  5740. LONG lTZBias, lTZHour, lTZMinute;
  5741. TIME_ZONE_INFORMATION tzi;
  5742. ULONG ulMessageSize;
  5743. char szCommandLine[CMDLINE_BUFSIZE];
  5744. CIMAPCmdInfo *piciCommand;
  5745. LPSTR p, pszUTF7MailboxName;
  5746. char cTZSign;
  5747. BOOL bResult;
  5748. // Check arguments
  5749. Assert(m_lRefCount > 0);
  5750. Assert(NULL != lpszMailboxName);
  5751. Assert(NULL != lpstmMessageToSave);
  5752. // Verify proper server state
  5753. // Only accept cmds if server is in proper state, OR if we're connecting,
  5754. // and the cmd requires Authenticated state or less (always TRUE in this case)
  5755. if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
  5756. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  5757. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  5758. }
  5759. // Initialize variables
  5760. pszUTF7MailboxName = NULL;
  5761. piciCommand = new CIMAPCmdInfo(this, icAPPEND_COMMAND, ssAuthenticated,
  5762. wParam, lParam, pCBHandler);
  5763. if (piciCommand == NULL) {
  5764. hrResult = E_OUTOFMEMORY;
  5765. goto error;
  5766. }
  5767. // Convert FILETIME to IMAP date/time spec
  5768. bResult = FileTimeToLocalFileTime(&ftMessageDateTime, &ftLocalTime);
  5769. if (bResult)
  5770. bResult = FileTimeToSystemTime(&ftLocalTime, &stMsgDateTime);
  5771. if (FALSE == bResult) {
  5772. Assert(FALSE); // Conversion failed
  5773. // If retail version, just substitute a default system time
  5774. stMsgDateTime = stDefaultDateTime;
  5775. }
  5776. // Figure out time zone (stolen from MsgOut.cpp's HrEmitDateTime)
  5777. dwTimeZoneId = GetTimeZoneInformation (&tzi);
  5778. switch (dwTimeZoneId)
  5779. {
  5780. case TIME_ZONE_ID_STANDARD:
  5781. lTZBias = tzi.Bias + tzi.StandardBias;
  5782. break;
  5783. case TIME_ZONE_ID_DAYLIGHT:
  5784. lTZBias = tzi.Bias + tzi.DaylightBias;
  5785. break;
  5786. case TIME_ZONE_ID_UNKNOWN:
  5787. default:
  5788. lTZBias = 0 ; // $$BUG: what's supposed to happen here?
  5789. break;
  5790. }
  5791. lTZHour = lTZBias / 60;
  5792. lTZMinute = lTZBias % 60;
  5793. cTZSign = (lTZHour < 0) ? '+' : '-';
  5794. // Get size of message
  5795. hrResult = HrGetStreamSize(lpstmMessageToSave, &ulMessageSize);
  5796. if (FAILED(hrResult))
  5797. goto error;
  5798. // Send command to server
  5799. // Format: tag APPEND mboxName msgFlags "dd-mmm-yyyy hh:mm:ss +/-hhmm" {msgSize}
  5800. p = szCommandLine;
  5801. p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s APPEND", piciCommand->szTag);
  5802. // Convert mailbox name to modified UTF-7
  5803. hrResult = _MultiByteToModifiedUTF7(lpszMailboxName, &pszUTF7MailboxName);
  5804. if (FAILED(hrResult))
  5805. goto error;
  5806. // Don't worry about long mailbox name overflow, long mbox names will be sent as literals
  5807. hrResult = AppendSendAString(piciCommand, szCommandLine, &p, sizeof(szCommandLine),
  5808. pszUTF7MailboxName);
  5809. if (FAILED(hrResult))
  5810. goto error;
  5811. if (NULL != lpszMessageFlags)
  5812. p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), " %.250s", lpszMessageFlags);
  5813. // Limited potential for buffer overflow: mailbox names over 128 bytes are sent as
  5814. // literals, so worst case buffer usage is 11+128+34+flags+literalNum=173+~20=~200
  5815. p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine),
  5816. " \"%2d-%.3s-%04d %02d:%02d:%02d %c%02d%02d\" {%lu}\r\n",
  5817. stMsgDateTime.wDay, lpszMonthsOfTheYear[stMsgDateTime.wMonth],
  5818. stMsgDateTime.wYear, stMsgDateTime.wHour, stMsgDateTime.wMinute,
  5819. stMsgDateTime.wSecond,
  5820. cTZSign, abs(lTZHour), abs(lTZMinute),
  5821. ulMessageSize);
  5822. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
  5823. if (FAILED(hrResult))
  5824. goto error;
  5825. // Don't have to wait for response... message body will be queued
  5826. hrResult = SendLiteral(piciCommand, lpstmMessageToSave, ulMessageSize);
  5827. if (FAILED(hrResult))
  5828. goto error;
  5829. // Have to send CRLF to end command line (the literal's CRLF doesn't count)
  5830. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, c_szCRLF, lstrlen(c_szCRLF));
  5831. if (FAILED(hrResult))
  5832. goto error;
  5833. // Transmit command and register with IMAP response parser
  5834. hrResult = SubmitIMAPCommand(piciCommand);
  5835. error:
  5836. if (FAILED(hrResult))
  5837. delete piciCommand;
  5838. if (NULL != pszUTF7MailboxName)
  5839. MemFree(pszUTF7MailboxName);
  5840. return hrResult;
  5841. } // Append
  5842. //***************************************************************************
  5843. // Function: Close
  5844. //
  5845. // Purpose:
  5846. // This function issues a CLOSE command to the IMAP server.
  5847. //
  5848. // Arguments:
  5849. // WPARAM wParam [in] - (see below)
  5850. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5851. // the caller to this IMAP command and its responses. Can be anything,
  5852. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5853. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5854. // responses for this command. If this is NULL, the default CB handler
  5855. // is used.
  5856. //
  5857. // Returns:
  5858. // HRESULT indicating success or failure of send operation.
  5859. //***************************************************************************
  5860. HRESULT STDMETHODCALLTYPE CImap4Agent::Close (WPARAM wParam, LPARAM lParam,
  5861. IIMAPCallback *pCBHandler)
  5862. {
  5863. return NoArgCommand("CLOSE", icCLOSE_COMMAND, ssSelected,
  5864. wParam, lParam, pCBHandler);
  5865. } // Close
  5866. //***************************************************************************
  5867. // Function: Expunge
  5868. //
  5869. // Purpose:
  5870. // This function issues an EXPUNGE command to the IMAP server.
  5871. //
  5872. // Arguments:
  5873. // WPARAM wParam [in] - (see below)
  5874. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5875. // the caller to this IMAP command and its responses. Can be anything,
  5876. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5877. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5878. // responses for this command. If this is NULL, the default CB handler
  5879. // is used.
  5880. //
  5881. // Returns:
  5882. // HRESULT indicating success or failure on send operation.
  5883. //***************************************************************************
  5884. HRESULT STDMETHODCALLTYPE CImap4Agent::Expunge (WPARAM wParam, LPARAM lParam,
  5885. IIMAPCallback *pCBHandler)
  5886. {
  5887. return NoArgCommand("EXPUNGE", icEXPUNGE_COMMAND, ssSelected,
  5888. wParam, lParam, pCBHandler);
  5889. } // Expunge
  5890. //***************************************************************************
  5891. // Function: Search
  5892. //
  5893. // Purpose:
  5894. // This function issues a SEARCH command to the IMAP server.
  5895. //
  5896. // Arguments:
  5897. // WPARAM wParam [in] - (see below)
  5898. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5899. // the caller to this IMAP command and its responses. Can be anything,
  5900. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5901. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5902. // responses for this command. If this is NULL, the default CB handler
  5903. // is used.
  5904. // LPSTR lpszSearchCriteria - IMAP-compliant list of search criteria
  5905. // boolean bReturnUIDs - if TRUE, we prepend "UID" to command.
  5906. // IRangeList *pMsgRange [in] - range of messages over which to operate
  5907. // the search. This argument should be NULL to exclude the message
  5908. // set from the search criteria.
  5909. // boolean bUIDRangeList [in] - TRUE if pMsgRange refers to a UID range,
  5910. // FALSE if pMsgRange refers to a message sequence number range. If
  5911. // pMsgRange is NULL, this argument is ignored.
  5912. //
  5913. // Returns:
  5914. // HRESULT indicating success or failure on send operation.
  5915. //***************************************************************************
  5916. HRESULT STDMETHODCALLTYPE CImap4Agent::Search(WPARAM wParam, LPARAM lParam,
  5917. IIMAPCallback *pCBHandler,
  5918. LPSTR lpszSearchCriteria,
  5919. boolean bReturnUIDs, IRangeList *pMsgRange,
  5920. boolean bUIDRangeList)
  5921. {
  5922. return RangedCommand("SEARCH", bReturnUIDs, pMsgRange, bUIDRangeList,
  5923. rcNOT_ASTRING_ARG, lpszSearchCriteria, icSEARCH_COMMAND, wParam,
  5924. lParam, pCBHandler);
  5925. } // Search
  5926. //***************************************************************************
  5927. // Function: Fetch
  5928. //
  5929. // Purpose:
  5930. // This function issues a FETCH command to the IMAP server.
  5931. //
  5932. // Arguments:
  5933. // WPARAM wParam [in] - (see below)
  5934. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5935. // the caller to this IMAP command and its responses. Can be anything,
  5936. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5937. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5938. // responses for this command. If this is NULL, the default CB handler
  5939. // is used.
  5940. // IRangeList *pMsgRange [in] - range of messages to fetch. The caller
  5941. // should pass NULL if he is using UIDs and he wants to generate his
  5942. // own message set (in lpszFetchArgs). If the caller is using msg
  5943. // seq nums, this argument MUST be specified to allow this class to
  5944. // resequence the msg nums as required.
  5945. // boolean bUIDMsgRange [in] - if TRUE, prepends "UID" to FETCH command and
  5946. // treats pMsgRange as a UID range.
  5947. // LPSTR lpszFetchArgs - arguments to the FETCH command
  5948. //
  5949. //
  5950. // Returns:
  5951. // HRESULT indicating success or failure of the send operation.
  5952. //***************************************************************************
  5953. HRESULT STDMETHODCALLTYPE CImap4Agent::Fetch(WPARAM wParam, LPARAM lParam,
  5954. IIMAPCallback *pCBHandler,
  5955. IRangeList *pMsgRange,
  5956. boolean bUIDMsgRange,
  5957. LPSTR lpszFetchArgs)
  5958. {
  5959. return RangedCommand("FETCH", bUIDMsgRange, pMsgRange, bUIDMsgRange,
  5960. rcNOT_ASTRING_ARG, lpszFetchArgs, icFETCH_COMMAND, wParam, lParam,
  5961. pCBHandler);
  5962. } // Fetch
  5963. //***************************************************************************
  5964. // Function: Store
  5965. //
  5966. // Purpose:
  5967. // This function issues a STORE command to the IMAP server.
  5968. //
  5969. // Arguments:
  5970. // WPARAM wParam [in] - (see below)
  5971. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  5972. // the caller to this IMAP command and its responses. Can be anything,
  5973. // but note that the value of 0, 0 is reserved for unsolicited responses.
  5974. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  5975. // responses for this command. If this is NULL, the default CB handler
  5976. // is used.
  5977. // IRangeList *pMsgRange [in] - range of messages to store. The caller
  5978. // should pass NULL if he is using UIDs and he wants to generate his
  5979. // own message set (in lpszStoreArgs). If the caller is using msg
  5980. // seq nums, this argument MUST be specified to allow this class to
  5981. // resequence the msg nums as required.
  5982. // boolean bUIDRangeList [in] - if TRUE, we prepend "UID" to the STORE command
  5983. // LPSTR lpszStoreArgs - arguments for the STORE command.
  5984. //
  5985. // Returns:
  5986. // HRESULT indicating success or failure of the send operation.
  5987. //***************************************************************************
  5988. HRESULT STDMETHODCALLTYPE CImap4Agent::Store(WPARAM wParam, LPARAM lParam,
  5989. IIMAPCallback *pCBHandler,
  5990. IRangeList *pMsgRange,
  5991. boolean bUIDRangeList,
  5992. LPSTR lpszStoreArgs)
  5993. {
  5994. return RangedCommand("STORE", bUIDRangeList, pMsgRange, bUIDRangeList,
  5995. rcNOT_ASTRING_ARG, lpszStoreArgs, icSTORE_COMMAND, wParam, lParam,
  5996. pCBHandler);
  5997. } // Store
  5998. //***************************************************************************
  5999. // Function: Copy
  6000. //
  6001. // Purpose:
  6002. // This function issues a COPY command to the IMAP server.
  6003. //
  6004. // Arguments:
  6005. // WPARAM wParam [in] - (see below)
  6006. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  6007. // the caller to this IMAP command and its responses. Can be anything,
  6008. // but note that the value of 0, 0 is reserved for unsolicited responses.
  6009. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  6010. // responses for this command. If this is NULL, the default CB handler
  6011. // is used.
  6012. // IRangeList *pMsgRange [in] - the range of messages to copy. This
  6013. // argument must be supplied.
  6014. // boolean bUIDRangeList [in] - if TRUE, prepends "UID" to COPY command
  6015. // LPSTR lpszMailboxName [in] - C String of mailbox name
  6016. //
  6017. // Returns:
  6018. // HRESULT indicating success or failure of send operation.
  6019. //***************************************************************************
  6020. HRESULT STDMETHODCALLTYPE CImap4Agent::Copy(WPARAM wParam, LPARAM lParam,
  6021. IIMAPCallback *pCBHandler,
  6022. IRangeList *pMsgRange,
  6023. boolean bUIDRangeList,
  6024. LPSTR lpszMailboxName)
  6025. {
  6026. HRESULT hrResult;
  6027. LPSTR pszUTF7MailboxName;
  6028. DWORD dwNumCharsWritten;
  6029. // Initialize variables
  6030. pszUTF7MailboxName = NULL;
  6031. // Convert the mailbox name to modified UTF-7
  6032. hrResult = _MultiByteToModifiedUTF7(lpszMailboxName, &pszUTF7MailboxName);
  6033. if (FAILED(hrResult))
  6034. goto exit;
  6035. hrResult = RangedCommand("COPY", bUIDRangeList, pMsgRange, bUIDRangeList,
  6036. rcASTRING_ARG, pszUTF7MailboxName, icCOPY_COMMAND, wParam, lParam,
  6037. pCBHandler);
  6038. exit:
  6039. if (NULL != pszUTF7MailboxName)
  6040. MemFree(pszUTF7MailboxName);
  6041. return hrResult;
  6042. } // Copy
  6043. //***************************************************************************
  6044. // Function: Status
  6045. //
  6046. // Purpose:
  6047. // This function issues a STATUS command to the IMAP server.
  6048. //
  6049. // Arguments:
  6050. // WPARAM wParam [in] - (see below)
  6051. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  6052. // the caller to this IMAP command and its responses. Can be anything,
  6053. // but note that the value of 0, 0 is reserved for unsolicited responses.
  6054. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  6055. // responses for this command. If this is NULL, the default CB handler
  6056. // is used.
  6057. // LPSTR pszMailboxName [in] - the mailbox which you want to get the
  6058. // STATUS of.
  6059. // LPSTR pszStatusCmdArgs [in] - the arguments for the STATUS command,
  6060. // eg, "(MESSAGES UNSEEN)".
  6061. //
  6062. // Returns:
  6063. // HRESULT indicating success or failure of send operation.
  6064. //***************************************************************************
  6065. HRESULT STDMETHODCALLTYPE CImap4Agent::Status(WPARAM wParam, LPARAM lParam,
  6066. IIMAPCallback *pCBHandler,
  6067. LPSTR pszMailboxName,
  6068. LPSTR pszStatusCmdArgs)
  6069. {
  6070. HRESULT hrResult;
  6071. CIMAPCmdInfo *piciCommand;
  6072. char szCommandLine[CMDLINE_BUFSIZE];
  6073. LPSTR p, pszUTF7MailboxName;
  6074. // Verify proper server state and set us up as current command
  6075. Assert(m_lRefCount > 0);
  6076. Assert(NULL != pszMailboxName);
  6077. Assert(NULL != pszStatusCmdArgs);
  6078. // Initialize variables
  6079. pszUTF7MailboxName = NULL;
  6080. // Verify proper server state
  6081. // Only accept cmds if server is in proper state, OR if we're connecting,
  6082. // and the cmd requires Authenticated state or less (always TRUE in this case)
  6083. if (ssAuthenticated > m_ssServerState && ssConnecting != m_ssServerState) {
  6084. AssertSz(FALSE, "The IMAP server is not in the correct state to accept this command.");
  6085. return IXP_E_IMAP_IMPROPER_SVRSTATE;
  6086. }
  6087. piciCommand = new CIMAPCmdInfo(this, icSTATUS_COMMAND, ssAuthenticated,
  6088. wParam, lParam, pCBHandler);
  6089. if (piciCommand == NULL) {
  6090. hrResult = E_OUTOFMEMORY;
  6091. goto error;
  6092. }
  6093. // Send STATUS command to server, wait for response
  6094. p = szCommandLine;
  6095. p += wnsprintf(szCommandLine, ARRAYSIZE(szCommandLine), "%s %s", piciCommand->szTag, "STATUS");
  6096. // Convert the mailbox name to modified UTF-7
  6097. hrResult = _MultiByteToModifiedUTF7(pszMailboxName, &pszUTF7MailboxName);
  6098. if (FAILED(hrResult))
  6099. goto error;
  6100. // Don't worry about long mailbox name overflow, long mbox names will be sent as literals
  6101. hrResult = AppendSendAString(piciCommand, szCommandLine, &p,
  6102. sizeof(szCommandLine), pszUTF7MailboxName);
  6103. if (FAILED(hrResult))
  6104. goto error;
  6105. // Limited overflow risk: since literal threshold is 128, max buffer usage is
  6106. // 11+128+2+args = 141+~20 = 161
  6107. p += wnsprintf(p, ARRAYSIZE(szCommandLine) - (DWORD)(p - szCommandLine), " %.300s\r\n", pszStatusCmdArgs);
  6108. hrResult = SendCmdLine(piciCommand, sclAPPEND_TO_END, szCommandLine, (DWORD) (p - szCommandLine));
  6109. if (FAILED(hrResult))
  6110. goto error;
  6111. // Transmit command and register with IMAP response parser
  6112. hrResult = SubmitIMAPCommand(piciCommand);
  6113. error:
  6114. if (NULL != pszUTF7MailboxName)
  6115. MemFree(pszUTF7MailboxName);
  6116. if (FAILED(hrResult))
  6117. delete piciCommand;
  6118. return hrResult;
  6119. } // Status
  6120. //***************************************************************************
  6121. // Function: Noop
  6122. //
  6123. // Purpose:
  6124. // This function issues a NOOP command to the IMAP server.
  6125. //
  6126. // Arguments:
  6127. // WPARAM wParam [in] - (see below)
  6128. // LPARAM lParam [in] - the wParam and lParam form a unique ID assigned by
  6129. // the caller to this IMAP command and its responses. Can be anything,
  6130. // but note that the value of 0, 0 is reserved for unsolicited responses.
  6131. // IIMAPCallback *pCBHandler [in] - the CB handler to use to process the
  6132. // responses for this command. If this is NULL, the default CB handler
  6133. // is used.
  6134. //
  6135. // Returns:
  6136. // HRESULT indicating success or failure of send operation.
  6137. //***************************************************************************
  6138. HRESULT STDMETHODCALLTYPE CImap4Agent::Noop(WPARAM wParam, LPARAM lParam,
  6139. IIMAPCallback *pCBHandler)
  6140. {
  6141. return NoArgCommand("NOOP", icNOOP_COMMAND, ssNonAuthenticated,
  6142. wParam, lParam, pCBHandler);
  6143. } // Noop
  6144. //***************************************************************************
  6145. // Function: EnterIdleMode
  6146. //
  6147. // Purpose:
  6148. // This function issues the IDLE command to the server, if the server
  6149. // supports this extension. It should be called when no commands are
  6150. // currently begin transmitted (or waiting to be transmitted) and no
  6151. // commands are expected back from the server. When the next IMAP command is
  6152. // issued, the send machine automatically leaves IDLE mode before issuing
  6153. // the IMAP command.
  6154. //***************************************************************************
  6155. void CImap4Agent::EnterIdleMode(void)
  6156. {
  6157. CIMAPCmdInfo *piciIdleCmd;
  6158. HRESULT hrResult;
  6159. char sz[NUM_TAG_CHARS + 7 + 1];
  6160. int i;
  6161. // Check if this server supports IDLE
  6162. if (0 == (m_dwCapabilityFlags & IMAP_CAPABILITY_IDLE))
  6163. return; // Nothing to do here
  6164. // Initialize variables
  6165. hrResult = S_OK;
  6166. piciIdleCmd = NULL;
  6167. piciIdleCmd = new CIMAPCmdInfo(this, icIDLE_COMMAND, ssAuthenticated,
  6168. 0, 0, NULL);
  6169. if (NULL == piciIdleCmd) {
  6170. hrResult = E_OUTOFMEMORY;
  6171. goto exit;
  6172. }
  6173. i = wnsprintf(sz, ARRAYSIZE(sz), "%.4s IDLE\r\n", piciIdleCmd->szTag);
  6174. Assert(11 == i);
  6175. hrResult = SendCmdLine(piciIdleCmd, sclAPPEND_TO_END, sz, i);
  6176. if (FAILED(hrResult))
  6177. goto exit;
  6178. hrResult = SendPause(piciIdleCmd);
  6179. if (FAILED(hrResult))
  6180. goto exit;
  6181. hrResult = SendCmdLine(piciIdleCmd, sclAPPEND_TO_END, c_szDONE,
  6182. sizeof(c_szDONE) - 1);
  6183. if (FAILED(hrResult))
  6184. goto exit;
  6185. hrResult = SendStop(piciIdleCmd);
  6186. if (FAILED(hrResult))
  6187. goto exit;
  6188. hrResult = SubmitIMAPCommand(piciIdleCmd);
  6189. exit:
  6190. if (FAILED(hrResult)) {
  6191. AssertSz(FALSE, "EnterIdleMode failure");
  6192. if (NULL != piciIdleCmd)
  6193. delete piciIdleCmd;
  6194. }
  6195. } // EnterIdleMode
  6196. //***************************************************************************
  6197. // Function: GenerateMsgSet
  6198. //
  6199. // Purpose:
  6200. // This function takes an array of message ID's (could be UIDs or Msg
  6201. // sequence numbers, this function doesn't care) and converts it to an IMAP
  6202. // set (see Formal Syntax in RFC1730). If the given array of message ID's is
  6203. // SORTED, this function can coalesce a run of numbers into a range. For
  6204. // unsorted arrays, it doesn't bother coalescing the numbers.
  6205. //
  6206. // Arguments:
  6207. // LPSTR lpszDestination [out] - output buffer for IMAP set. NOTE that the
  6208. // output string deposited here has a leading comma which must be
  6209. // DWORD dwSizeOfDestination [in] - size of output buffer.
  6210. // DWORD *pMsgID [in] - pointer to an array of message ID's (UIDs or Msg
  6211. // sequence numbers)
  6212. // DWORD cMsgID [in] - the number of message ID's passed in the *pMsgID
  6213. // array.
  6214. //
  6215. // Returns:
  6216. // DWORD indicating the number of characters written. Adding this value
  6217. // to the value of lpszDestination will point to the null-terminator at
  6218. // the end of the output string.
  6219. //***************************************************************************
  6220. DWORD CImap4Agent::GenerateMsgSet(LPSTR lpszDestination,
  6221. DWORD dwSizeOfDestination,
  6222. DWORD *pMsgID,
  6223. DWORD cMsgID)
  6224. {
  6225. LPSTR p;
  6226. DWORD dwNumMsgsCopied, idStartOfRange, idEndOfRange;
  6227. DWORD dwNumMsgsInRun; // Used to detect if we are in a run of consecutive nums
  6228. boolean bFirstRange; // TRUE if outputting first msg range in set
  6229. Assert(m_lRefCount > 0);
  6230. Assert(NULL != lpszDestination);
  6231. Assert(0 != dwSizeOfDestination);
  6232. Assert(NULL != pMsgID);
  6233. Assert(0 != cMsgID);
  6234. // Construct the set of messages to copy
  6235. p = lpszDestination;
  6236. DWORD cchSize = dwSizeOfDestination;
  6237. idStartOfRange = *pMsgID;
  6238. dwNumMsgsInRun = 0;
  6239. bFirstRange = TRUE; // Suppress leading comma for first message range in set
  6240. for (dwNumMsgsCopied = 0; dwNumMsgsCopied < cMsgID; dwNumMsgsCopied++ ) {
  6241. if (*pMsgID == idStartOfRange + dwNumMsgsInRun) {
  6242. idEndOfRange = *pMsgID; // Construct a range out of consecutive numbers
  6243. dwNumMsgsInRun += 1;
  6244. }
  6245. else {
  6246. // No more consecutive numbers found, output the range
  6247. cchSize = (DWORD)(dwSizeOfDestination - (p - lpszDestination));
  6248. AppendMsgRange(&p, cchSize, idStartOfRange, idEndOfRange, bFirstRange);
  6249. idStartOfRange = *pMsgID;
  6250. idEndOfRange = *pMsgID;
  6251. dwNumMsgsInRun = 1;
  6252. bFirstRange = FALSE; // Turn on leading comma from this point on
  6253. }
  6254. pMsgID += 1;
  6255. } // for
  6256. if (dwNumMsgsInRun > 0)
  6257. {
  6258. // Perform append for last msgID
  6259. cchSize = (dwSizeOfDestination - (DWORD)(p - lpszDestination));
  6260. AppendMsgRange(&p, cchSize, idStartOfRange, idEndOfRange, bFirstRange);
  6261. }
  6262. // Check for buffer overflow
  6263. Assert(p < lpszDestination + dwSizeOfDestination);
  6264. return (DWORD) (p - lpszDestination);
  6265. } // GenerateMsgSet
  6266. //***************************************************************************
  6267. // Function: AppendMsgRange
  6268. //
  6269. // Purpose:
  6270. // This function appends a single message range to the given string
  6271. // pointer, either in the form ",seq num" or ",seq num:seq num" (NOTE the
  6272. // leading comma: this should be suppressed for the first message range in
  6273. // the set by setting bSuppressComma to TRUE).
  6274. //
  6275. // Arguments:
  6276. // LPSTR *ppDest [in/out] - this pointer should always point to the end
  6277. // of the string currently being constructed, although there need not be
  6278. // a null-terminator present. After this function appends its message
  6279. // range to the string, it advances *ppDest by the correct amount.
  6280. // Note that it is the caller's responsibility to perform bounds checking.
  6281. // const DWORD idStartOfRange [in] - the first msg number of the msg range.
  6282. // const DWORD idEndOfRange [in] - the last msg number of the msg range.
  6283. // const boolean bSuppressComma [in] - TRUE if the leading comma should be
  6284. // suppressed. This is generally TRUE only for the first message range
  6285. // in the set.
  6286. //
  6287. // Returns:
  6288. // Nothing. Given valid arguments, this function cannot fail.
  6289. //***************************************************************************
  6290. void CImap4Agent::AppendMsgRange(LPSTR *ppDest, const DWORD cchSizeDest, const DWORD idStartOfRange,
  6291. const DWORD idEndOfRange, boolean bSuppressComma)
  6292. {
  6293. LPSTR lpszComma;
  6294. int numCharsWritten;
  6295. Assert(m_lRefCount > 0);
  6296. Assert(NULL != ppDest);
  6297. Assert(NULL != *ppDest);
  6298. Assert(0 != idStartOfRange); // MSGIDs are never zero in IMAP-land
  6299. Assert(0 != idEndOfRange);
  6300. if (TRUE == bSuppressComma)
  6301. lpszComma = "";
  6302. else
  6303. lpszComma = ",";
  6304. if (idStartOfRange == idEndOfRange)
  6305. // Single message number
  6306. numCharsWritten = wnsprintf(*ppDest, cchSizeDest, "%s%lu", lpszComma, idStartOfRange);
  6307. else
  6308. // Range of consecutive message numbers
  6309. numCharsWritten = wnsprintf(*ppDest, cchSizeDest, "%s%lu:%lu", lpszComma,
  6310. idStartOfRange, idEndOfRange);
  6311. *ppDest += numCharsWritten;
  6312. } // AppendMsgRange
  6313. //***************************************************************************
  6314. // Function: EnqueueFragment
  6315. //
  6316. // Purpose:
  6317. // This function takes an IMAP_LINE_FRAGMENT and appends it to the end of
  6318. // the given IMAP_LINEFRAG_QUEUE.
  6319. //
  6320. // Arguments:
  6321. // IMAP_LINE_FRAGMENT *pilfSourceFragment [in] - a pointer to the line
  6322. // fragment to enqueue in the given line fragment queue. This can be
  6323. // a single line fragment (with the pilfNextFragment member set to NULL),
  6324. // or a chain of line fragments.
  6325. // IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
  6326. // fragment queue which the given line fragment(s) should be appended to.
  6327. //
  6328. // Returns:
  6329. // Nothing. Given valid arguments, this function cannot fail.
  6330. //***************************************************************************
  6331. void CImap4Agent::EnqueueFragment(IMAP_LINE_FRAGMENT *pilfSourceFragment,
  6332. IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
  6333. {
  6334. IMAP_LINE_FRAGMENT *pilfLast;
  6335. Assert(m_lRefCount > 0);
  6336. Assert(NULL != pilfSourceFragment);
  6337. Assert(NULL != pilqLineFragQueue);
  6338. // Check for empty queue
  6339. pilfSourceFragment->pilfPrevFragment = pilqLineFragQueue->pilfLastFragment;
  6340. if (NULL == pilqLineFragQueue->pilfLastFragment) {
  6341. Assert(NULL == pilqLineFragQueue->pilfFirstFragment); // True test for emptiness
  6342. pilqLineFragQueue->pilfFirstFragment = pilfSourceFragment;
  6343. }
  6344. else
  6345. pilqLineFragQueue->pilfLastFragment->pilfNextFragment = pilfSourceFragment;
  6346. // Find end of queue
  6347. pilfLast = pilfSourceFragment;
  6348. while (NULL != pilfLast->pilfNextFragment)
  6349. pilfLast = pilfLast->pilfNextFragment;
  6350. pilqLineFragQueue->pilfLastFragment = pilfLast;
  6351. } // EnqueueFragment
  6352. //***************************************************************************
  6353. // Function: InsertFragmentBeforePause
  6354. //
  6355. // Purpose:
  6356. // This function inserts the given IMAP line fragment into the given
  6357. // linefrag queue, before the first iltPAUSE element that it finds. If no
  6358. // iltPAUSE fragment could be found, the line fragment is added to the end.
  6359. //
  6360. // Arguments:
  6361. // IMAP_LINE_FRAGMENT *pilfSourceFragment [in] - a pointer to the line
  6362. // fragment to insert before the iltPAUSE element in the given line
  6363. // fragment queue. This can be a single line fragment (with the
  6364. // pilfNextFragment member set to NULL), or a chain of line fragments.
  6365. // IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
  6366. // fragment queue which contains the iltPAUSE element.
  6367. //***************************************************************************
  6368. void CImap4Agent::InsertFragmentBeforePause(IMAP_LINE_FRAGMENT *pilfSourceFragment,
  6369. IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
  6370. {
  6371. IMAP_LINE_FRAGMENT *pilfInsertionPt, *pilfPause;
  6372. Assert(m_lRefCount > 0);
  6373. Assert(NULL != pilfSourceFragment);
  6374. Assert(NULL != pilqLineFragQueue);
  6375. // Look for the iltPAUSE fragment in the linefrag queue
  6376. pilfInsertionPt = NULL;
  6377. pilfPause = pilqLineFragQueue->pilfFirstFragment;
  6378. while (NULL != pilfPause && iltPAUSE != pilfPause->iltFragmentType) {
  6379. pilfInsertionPt = pilfPause;
  6380. pilfPause = pilfPause->pilfNextFragment;
  6381. }
  6382. if (NULL == pilfPause) {
  6383. // Didn't find iltPAUSE fragment, insert at tail of queue
  6384. AssertSz(FALSE, "Didn't find iltPAUSE fragment! WHADDUP?");
  6385. EnqueueFragment(pilfSourceFragment, pilqLineFragQueue);
  6386. }
  6387. else {
  6388. IMAP_LINE_FRAGMENT *pilfLast;
  6389. // Find the end of the source fragment
  6390. pilfLast = pilfSourceFragment;
  6391. while (NULL != pilfLast->pilfNextFragment)
  6392. pilfLast = pilfLast->pilfNextFragment;
  6393. // Found an iltPAUSE fragment. Insert the line fragment in front of it
  6394. pilfLast->pilfNextFragment = pilfPause;
  6395. Assert(pilfInsertionPt == pilfPause->pilfPrevFragment);
  6396. pilfPause->pilfPrevFragment = pilfLast;
  6397. if (NULL == pilfInsertionPt) {
  6398. // Insert at the head of the linefrag queue
  6399. Assert(pilfPause == pilqLineFragQueue->pilfFirstFragment);
  6400. pilfSourceFragment->pilfPrevFragment = NULL;
  6401. pilqLineFragQueue->pilfFirstFragment = pilfSourceFragment;
  6402. }
  6403. else {
  6404. // Insert in middle of queue
  6405. Assert(pilfPause == pilfInsertionPt->pilfNextFragment);
  6406. pilfSourceFragment->pilfPrevFragment = pilfInsertionPt;
  6407. pilfInsertionPt->pilfNextFragment = pilfSourceFragment;
  6408. }
  6409. }
  6410. } // InsertFragmentBeforePause
  6411. //***************************************************************************
  6412. // Function: DequeueFragment
  6413. //
  6414. // Purpose:
  6415. // This function returns the next line fragment from the given line
  6416. // fragment queue, removing the returned element from the queue.
  6417. //
  6418. // Arguments:
  6419. // IMAP_LINEFRAG_QUEUE *pilqLineFragQueue [in] - a pointer to the line
  6420. // fragment queue to dequeue from.
  6421. //
  6422. // Returns:
  6423. // A pointer to an IMAP_LINE_FRAGMENT. If none are available, NULL is
  6424. // returned.
  6425. //***************************************************************************
  6426. IMAP_LINE_FRAGMENT *CImap4Agent::DequeueFragment(IMAP_LINEFRAG_QUEUE *pilqLineFragQueue)
  6427. {
  6428. IMAP_LINE_FRAGMENT *pilfResult;
  6429. // Refcount can be 0 if we're destructing CImap4Agent while a cmd is in progress
  6430. Assert(m_lRefCount >= 0);
  6431. Assert(NULL != pilqLineFragQueue);
  6432. // Return element at head of queue, including NULL if empty queue
  6433. pilfResult = pilqLineFragQueue->pilfFirstFragment;
  6434. if (NULL != pilfResult) {
  6435. // Dequeue the element from list
  6436. pilqLineFragQueue->pilfFirstFragment = pilfResult->pilfNextFragment;
  6437. if (NULL == pilqLineFragQueue->pilfFirstFragment)
  6438. // Queue is now empty, so reset ptr to last fragment
  6439. pilqLineFragQueue->pilfLastFragment = NULL;
  6440. else {
  6441. Assert(pilfResult == pilqLineFragQueue->pilfFirstFragment->pilfPrevFragment);
  6442. pilqLineFragQueue->pilfFirstFragment->pilfPrevFragment = NULL;
  6443. }
  6444. pilfResult->pilfNextFragment = NULL;
  6445. pilfResult->pilfPrevFragment = NULL;
  6446. }
  6447. else {
  6448. AssertSz(FALSE, "Someone just tried to dequeue an element from empty queue");
  6449. }
  6450. return pilfResult;
  6451. } // DequeueFragment
  6452. //***************************************************************************
  6453. // Function: FreeFragment
  6454. //
  6455. // Purpose:
  6456. // This function frees the given IMAP line fragment and the string or
  6457. // stream data associated with it.
  6458. //
  6459. // Arguments:
  6460. // IMAP_LINE_FRAGMENT **ppilfFragment [in/out] - a pointer to the line
  6461. // fragment to free. The pointer is set to NULL after the fragment
  6462. // is freed.
  6463. //
  6464. // Returns:
  6465. // Nothing. Given valid arguments, this function cannot fail.
  6466. //***************************************************************************
  6467. void CImap4Agent::FreeFragment(IMAP_LINE_FRAGMENT **ppilfFragment)
  6468. {
  6469. // Refcount can be 0 if we're destructing CImap4Agent while a cmd is in progress
  6470. Assert(m_lRefCount >= 0);
  6471. Assert(NULL != ppilfFragment);
  6472. Assert(NULL != *ppilfFragment);
  6473. if (iltRANGELIST == (*ppilfFragment)->iltFragmentType) {
  6474. (*ppilfFragment)->data.prlRangeList->Release();
  6475. }
  6476. else if (ilsSTREAM == (*ppilfFragment)->ilsLiteralStoreType) {
  6477. Assert(iltLITERAL == (*ppilfFragment)->iltFragmentType);
  6478. (*ppilfFragment)->data.pstmSource->Release();
  6479. }
  6480. else {
  6481. Assert(ilsSTRING == (*ppilfFragment)->ilsLiteralStoreType);
  6482. SafeMemFree((*ppilfFragment)->data.pszSource);
  6483. }
  6484. delete *ppilfFragment;
  6485. *ppilfFragment = NULL;
  6486. } // FreeFragment
  6487. //***************************************************************************
  6488. // Function: SubmitIMAPCommand
  6489. //
  6490. // Purpose:
  6491. // This function takes a completed CIMAPCmdInfo structure (with completed
  6492. // command line) and submits it for transmittal to the IMAP server.
  6493. //
  6494. // Arguments:
  6495. // CIMAPCmdInfo *piciCommand [in] - this is the completed CIMAPCmdInfo
  6496. // structure to transmit to the IMAP server.
  6497. //
  6498. // Returns:
  6499. // HRESULT indicating success or failure.
  6500. //***************************************************************************
  6501. HRESULT CImap4Agent::SubmitIMAPCommand(CIMAPCmdInfo *piciCommand)
  6502. {
  6503. Assert(m_lRefCount > 0);
  6504. Assert(NULL != piciCommand);
  6505. // SubmitIMAPCommand is used to send all commands to the IMAP server.
  6506. // This is a good time to clear m_szLastResponseText
  6507. *m_szLastResponseText = '\0';
  6508. // If currently transmitted command is a paused IDLE command, unpause us
  6509. if (m_fIDLE && NULL != m_piciCmdInSending &&
  6510. icIDLE_COMMAND == m_piciCmdInSending->icCommandID &&
  6511. iltPAUSE == m_piciCmdInSending->pilqCmdLineQueue->pilfFirstFragment->iltFragmentType)
  6512. ProcessSendQueue(iseUNPAUSE);
  6513. // Enqueue the command into the send queue
  6514. EnterCriticalSection(&m_csSendQueue);
  6515. if (NULL == m_piciSendQueue)
  6516. // Insert command into empty queue
  6517. m_piciSendQueue = piciCommand;
  6518. else {
  6519. CIMAPCmdInfo *pici;
  6520. // Find the end of the send queue
  6521. pici = m_piciSendQueue;
  6522. while (NULL != pici->piciNextCommand)
  6523. pici = pici->piciNextCommand;
  6524. pici->piciNextCommand = piciCommand;
  6525. }
  6526. LeaveCriticalSection(&m_csSendQueue);
  6527. // Command is queued: kickstart its send process
  6528. ProcessSendQueue(iseSEND_COMMAND);
  6529. return S_OK;
  6530. } // SubmitIMAPCommand
  6531. //***************************************************************************
  6532. // Function: DequeueCommand
  6533. //
  6534. // Purpose:
  6535. // This function removes the command currently being sent from the send
  6536. // queue and returns a pointer to it.
  6537. //
  6538. // Returns:
  6539. // Pointer to CIMAPCmdInfo object if successful, otherwise NULL.
  6540. //***************************************************************************
  6541. CIMAPCmdInfo *CImap4Agent::DequeueCommand(void)
  6542. {
  6543. CIMAPCmdInfo *piciResult;
  6544. Assert(m_lRefCount > 0);
  6545. EnterCriticalSection(&m_csSendQueue);
  6546. piciResult = m_piciCmdInSending;
  6547. m_piciCmdInSending = NULL;
  6548. if (NULL != piciResult) {
  6549. CIMAPCmdInfo *piciCurrent, *piciPrev;
  6550. // Find the command in sending in the send queue
  6551. piciCurrent = m_piciSendQueue;
  6552. piciPrev = NULL;
  6553. while (NULL != piciCurrent) {
  6554. if (piciCurrent == piciResult)
  6555. break; // Found the current command in sending
  6556. piciPrev = piciCurrent;
  6557. piciCurrent = piciCurrent->piciNextCommand;
  6558. }
  6559. // Unlink the command from the send queue
  6560. if (NULL == piciPrev)
  6561. // Unlink command from the head of the send queue
  6562. m_piciSendQueue = m_piciSendQueue->piciNextCommand;
  6563. else if (NULL != piciCurrent)
  6564. // Unlink command from the middle/end of the queue
  6565. piciPrev->piciNextCommand = piciCurrent->piciNextCommand;
  6566. }
  6567. LeaveCriticalSection(&m_csSendQueue);
  6568. return piciResult;
  6569. } // DequeueCommand
  6570. //***************************************************************************
  6571. // Function: AddPendingCommand
  6572. //
  6573. // Purpose:
  6574. // This function adds the given CIMAPCmdInfo object to the list of commands
  6575. // pending server responses.
  6576. //
  6577. // Arguments:
  6578. // CIMAPCmdInfo *piciNewCommand [in] - pointer to command to add to list.
  6579. //***************************************************************************
  6580. void CImap4Agent::AddPendingCommand(CIMAPCmdInfo *piciNewCommand)
  6581. {
  6582. Assert(m_lRefCount > 0);
  6583. // Just insert at the head of the list
  6584. EnterCriticalSection(&m_csPendingList);
  6585. piciNewCommand->piciNextCommand = m_piciPendingList;
  6586. m_piciPendingList = piciNewCommand;
  6587. LeaveCriticalSection(&m_csPendingList);
  6588. } // AddPendingCommand
  6589. //***************************************************************************
  6590. // Function: RemovePendingCommand
  6591. //
  6592. // Purpose:
  6593. // This function looks for a command in the pending command list which
  6594. // matches the given tag. If found, it unlinks the CIMAPCmdInfo object from
  6595. // the list and returns a pointer to it.
  6596. //
  6597. // Arguments:
  6598. // LPSTR pszTag [in] - the tag of the command which should be removed.
  6599. //
  6600. // Returns:
  6601. // Pointer to CIMAPCmdInfo object if successful, otherwise NULL.
  6602. //***************************************************************************
  6603. CIMAPCmdInfo *CImap4Agent::RemovePendingCommand(LPSTR pszTag)
  6604. {
  6605. CIMAPCmdInfo *piciPrev, *piciCurrent;
  6606. boolean bFoundMatch;
  6607. boolean fLeaveBusy = FALSE;
  6608. Assert(m_lRefCount > 0);
  6609. Assert(NULL != pszTag);
  6610. EnterCriticalSection(&m_csPendingList);
  6611. // Look for matching tag in pending command list
  6612. bFoundMatch = FALSE;
  6613. piciPrev = NULL;
  6614. piciCurrent = m_piciPendingList;
  6615. while (NULL != piciCurrent) {
  6616. if (0 == lstrcmp(pszTag, piciCurrent->szTag)) {
  6617. bFoundMatch = TRUE;
  6618. break;
  6619. }
  6620. // Advance ptrs
  6621. piciPrev = piciCurrent;
  6622. piciCurrent = piciCurrent->piciNextCommand;
  6623. }
  6624. if (FALSE == bFoundMatch)
  6625. goto exit;
  6626. // OK, we found the matching command. Unlink it from list
  6627. if (NULL == piciPrev)
  6628. // Unlink first element in pending list
  6629. m_piciPendingList = piciCurrent->piciNextCommand;
  6630. else
  6631. // Unlink element from middle/end of list
  6632. piciPrev->piciNextCommand = piciCurrent->piciNextCommand;
  6633. // If we have removed the last pending command and no commands are being
  6634. // transmitted, it's time to leave the busy section
  6635. if (NULL == m_piciPendingList && NULL == m_piciCmdInSending)
  6636. fLeaveBusy = TRUE;
  6637. exit:
  6638. LeaveCriticalSection(&m_csPendingList);
  6639. // Now we're out of &m_csPendingList, call LeaveBusy (needs m_cs). Avoids deadlock.
  6640. if (fLeaveBusy)
  6641. LeaveBusy(); // Typically not needed, anymore
  6642. if (NULL != piciCurrent)
  6643. piciCurrent->piciNextCommand = NULL;
  6644. return piciCurrent;
  6645. } // RemovePendingCommand
  6646. //***************************************************************************
  6647. // Function: GetTransactionID
  6648. //
  6649. // Purpose:
  6650. // This function maps an IMAP_RESPONSE_ID to a transaction ID. This function
  6651. // takes the given IMAP_RESPONSE_ID and compares it with the IMAP command(s)
  6652. // currently pending a response. If the given response matches ONE (and only
  6653. // one) of the pending IMAP commands, then the transaction ID of that IMAP
  6654. // command is returned. If none or more than one match the given response,
  6655. // or if the response in general is unsolicited, then a value of 0 is
  6656. // returned.
  6657. //
  6658. // Arguments:
  6659. // WPARAM *pwParam [out] - the wParam for the given response. If conflicts
  6660. // could not be resolved, then a value of 0 is returned.
  6661. // LPARAM *plParam [out] - the lParam for the given response. If conflicts
  6662. // could not be resolved, then a value of 0 is returned.
  6663. // IIMAPCallback **ppCBHandler [out] - the CB Handler for a given response.
  6664. // If conflicts could not be resolved, or if a NULL CB Handler was
  6665. // specified for the associated command, the default CB handler is returned.
  6666. // IMAP_RESPONSE_ID irResponseType [in] - the response type for which the
  6667. // wants a transaction ID.
  6668. //***************************************************************************
  6669. void CImap4Agent::GetTransactionID(WPARAM *pwParam, LPARAM *plParam,
  6670. IIMAPCallback **ppCBHandler,
  6671. IMAP_RESPONSE_ID irResponseType)
  6672. {
  6673. WPARAM wParam;
  6674. LPARAM lParam;
  6675. IIMAPCallback *pCBHandler;
  6676. Assert(m_lRefCount > 0);
  6677. wParam = 0;
  6678. lParam = 0;
  6679. pCBHandler = m_pCBHandler;
  6680. switch (irResponseType) {
  6681. // The following responses are ALWAYS expected, regardless of cmd
  6682. case irOK_RESPONSE:
  6683. case irNO_RESPONSE:
  6684. case irBAD_RESPONSE:
  6685. case irNONE: // Usually indicates parsing error (reported via ErrorNotification CB)
  6686. FindTransactionID(&wParam, &lParam, &pCBHandler, icALL_COMMANDS);
  6687. break; // Always treat as solicited, so caller can associate with cmd
  6688. // The following responses are always unsolicited, either because
  6689. // they really ARE always unsolicited, or we don't care, or we want
  6690. // to encourage the client to expect a given response at all times
  6691. case irALERT_RESPONSECODE: // Clearly unsolicited
  6692. case irPARSE_RESPONSECODE: // Clearly unsolicited
  6693. case irPREAUTH_RESPONSE: // Clearly unsolicited
  6694. case irEXPUNGE_RESPONSE: // Client can get this any time, so get used to it
  6695. case irCMD_CONTINUATION: // No callback involved, don't care
  6696. case irBYE_RESPONSE: // Can happen at any time
  6697. case irEXISTS_RESPONSE: // Client can get this any time, so get used to it
  6698. case irRECENT_RESPONSE: // Client can get this any time, so get used to it
  6699. case irUNSEEN_RESPONSECODE: // Client can get this any time, so get used to it
  6700. case irSTATUS_RESPONSE:
  6701. break; // Always treated as unsolicited
  6702. // The following response types are considered solicited only for
  6703. // certain commands. Otherwise, they're unsolicited.
  6704. case irFLAGS_RESPONSE:
  6705. case irPERMANENTFLAGS_RESPONSECODE:
  6706. case irREADWRITE_RESPONSECODE:
  6707. case irREADONLY_RESPONSECODE:
  6708. case irUIDVALIDITY_RESPONSECODE:
  6709. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6710. icSELECT_COMMAND, icEXAMINE_COMMAND);
  6711. break; // case irFLAGS_RESPONSE
  6712. case irCAPABILITY_RESPONSE:
  6713. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6714. icCAPABILITY_COMMAND);
  6715. break; // case irCAPABILITY_RESPONSE
  6716. case irLIST_RESPONSE:
  6717. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6718. icLIST_COMMAND);
  6719. break; // case irLIST_RESPONSE
  6720. case irLSUB_RESPONSE:
  6721. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6722. icLSUB_COMMAND);
  6723. break; // case irLSUB_RESPONSE
  6724. case irSEARCH_RESPONSE:
  6725. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6726. icSEARCH_COMMAND);
  6727. break; // case irSEARCH_RESPONSE
  6728. case irFETCH_RESPONSE:
  6729. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6730. icFETCH_COMMAND, icSTORE_COMMAND);
  6731. break; // case irFETCH_RESPONSE
  6732. case irTRYCREATE_RESPONSECODE:
  6733. FindTransactionID(&wParam, &lParam, &pCBHandler,
  6734. icAPPEND_COMMAND, icCOPY_COMMAND);
  6735. break; // case irTRYCREATE_RESPONSECODE
  6736. default:
  6737. Assert(FALSE);
  6738. break; // default case
  6739. } // switch (irResponseType)
  6740. *pwParam = wParam;
  6741. *plParam = lParam;
  6742. *ppCBHandler = pCBHandler;
  6743. } // GetTransactionID
  6744. //***************************************************************************
  6745. // Function: FindTransactionID
  6746. //
  6747. // Purpose:
  6748. // This function traverses the pending command list searching for commands
  6749. // which match the command types specified in the arguments. If ONE (and only
  6750. // one) match is found, then its transaction ID is returne. If none or more
  6751. // than one match is found, then a transaction ID of 0 is returned.
  6752. //
  6753. // Arguments:
  6754. // WPARAM *pwParam [out] - the wParam for the given commands. If conflicts
  6755. // could not be resolved, then a value of 0 is returned. Pass NULL if
  6756. // you are not interested in this value.
  6757. // LPARAM *plParam [out] - the lParam for the given commands. If conflicts
  6758. // could not be resolved, then a value of 0 is returned. Pass NULL if
  6759. // you are not interested in this value.
  6760. // IIMAPCallback **ppCBHandler [out] - the CB Handler for a given response.
  6761. // If conflicts could not be resolved, or if a NULL CB Handler was
  6762. // specified for the associated command, the default CB handler is returned.
  6763. // Pass NULL if you are not interested in this value.
  6764. // IMAP_COMMAND icTarget1 [in] - one of the commands we're looking for in
  6765. // the pending command queue.
  6766. // IMAP_COMMAND icTarget2 [in] - another command we're looking for in
  6767. // the pending command queue.
  6768. //
  6769. // Returns:
  6770. // 0 if no matches were found
  6771. // 1 if exactly one match was found
  6772. // 2 if two matches was found. Note that there may be MORE than two matches
  6773. // in the pending list. This function gives up after it finds two matches.
  6774. //***************************************************************************
  6775. WORD CImap4Agent::FindTransactionID (WPARAM *pwParam, LPARAM *plParam,
  6776. IIMAPCallback **ppCBHandler,
  6777. IMAP_COMMAND icTarget1, IMAP_COMMAND icTarget2)
  6778. {
  6779. CIMAPCmdInfo *piciCurrentCmd;
  6780. WPARAM wParam;
  6781. LPARAM lParam;
  6782. IIMAPCallback *pCBHandler;
  6783. WORD wNumberOfMatches;
  6784. boolean bMatchAllCmds;
  6785. Assert(m_lRefCount > 0);
  6786. if (icALL_COMMANDS == icTarget1 ||
  6787. icALL_COMMANDS == icTarget2)
  6788. bMatchAllCmds = TRUE;
  6789. else
  6790. bMatchAllCmds = FALSE;
  6791. wNumberOfMatches = 0;
  6792. wParam = 0;
  6793. lParam = 0;
  6794. pCBHandler = m_pCBHandler;
  6795. EnterCriticalSection(&m_csPendingList);
  6796. piciCurrentCmd = m_piciPendingList;
  6797. while (NULL != piciCurrentCmd) {
  6798. if (bMatchAllCmds ||
  6799. icTarget1 == piciCurrentCmd->icCommandID ||
  6800. icTarget2 == piciCurrentCmd->icCommandID) {
  6801. wParam = piciCurrentCmd->wParam;
  6802. lParam = piciCurrentCmd->lParam;
  6803. pCBHandler = piciCurrentCmd->pCBHandler;
  6804. wNumberOfMatches += 1;
  6805. }
  6806. if (wNumberOfMatches > 1) {
  6807. wParam = 0;
  6808. lParam = 0;
  6809. pCBHandler = m_pCBHandler; // Found more than one match, can't resolve transaction ID
  6810. break;
  6811. }
  6812. piciCurrentCmd = piciCurrentCmd->piciNextCommand;
  6813. }
  6814. LeaveCriticalSection(&m_csPendingList);
  6815. if (NULL != pwParam)
  6816. *pwParam = wParam;
  6817. if (NULL != plParam)
  6818. *plParam = lParam;
  6819. if (NULL != ppCBHandler)
  6820. *ppCBHandler = pCBHandler;
  6821. return wNumberOfMatches;
  6822. } // FindTransactionID
  6823. //===========================================================================
  6824. // Message Sequence Number to UID Conversion Code
  6825. //===========================================================================
  6826. //***************************************************************************
  6827. // Function: NewIRangeList
  6828. //
  6829. // Purpose:
  6830. // This function returns a pointer to an IRangeList. Its purpose is to
  6831. // allow full functionality from an IIMAPTransport pointer without needing
  6832. // to resort to CoCreateInstance to get an IRangeList.
  6833. //
  6834. // Arguments:
  6835. // IRangeList **pprlNewRangeList [out] - if successful, the function
  6836. // returns a pointer to the new IRangeList.
  6837. //
  6838. // Returns:
  6839. // HRESULT indicating success or failure.
  6840. //***************************************************************************
  6841. HRESULT STDMETHODCALLTYPE CImap4Agent::NewIRangeList(IRangeList **pprlNewRangeList)
  6842. {
  6843. if (NULL == pprlNewRangeList)
  6844. return E_INVALIDARG;
  6845. *pprlNewRangeList = (IRangeList *) new CRangeList;
  6846. if (NULL == *pprlNewRangeList)
  6847. return E_OUTOFMEMORY;
  6848. return S_OK;
  6849. } // NewIRangeList
  6850. //***************************************************************************
  6851. // Function: OnIMAPError
  6852. //
  6853. // Purpose:
  6854. // This function calls ITransportCallback::OnError with the given info.
  6855. //
  6856. // Arguments:
  6857. // HRESULT hrResult [in] - the error code to use for IXPRESULT::hrResult.
  6858. // LPSTR pszFailureText [in] - a text string describing the failure. This
  6859. // is duplicated for IXPRESULT::pszProblem.
  6860. // BOOL bIncludeLastResponse [in] - if TRUE, this function duplicates
  6861. // the contents of m_szLastResponseText into IXPRESULT::pszResponse.
  6862. // If FALSE, IXPRESULT::pszResponse is left blank. Generally,
  6863. // m_szLastResponseText holds valid information only for errors which
  6864. // occur during the receipt of an IMAP response. Transmit errors should
  6865. // set this argument to FALSE.
  6866. // LPSTR pszDetails [in] - if bIncludeLastResponse is FALSE, the caller
  6867. // may pass a string to place into IXPRESULT::pszResponse here. If none
  6868. // is desired, the user should pass NULL.
  6869. //***************************************************************************
  6870. void CImap4Agent::OnIMAPError(HRESULT hrResult, LPSTR pszFailureText,
  6871. BOOL bIncludeLastResponse, LPSTR pszDetails)
  6872. {
  6873. IXPRESULT rIxpResult;
  6874. if (NULL == m_pCallback)
  6875. return; // We can't do a damned thing (this can happen due to HandsOffCallback)
  6876. // Save current state
  6877. rIxpResult.hrResult = hrResult;
  6878. if (bIncludeLastResponse) {
  6879. AssertSz(NULL == pszDetails, "Can't have it both ways, buddy!");
  6880. rIxpResult.pszResponse = PszDupA(m_szLastResponseText);
  6881. }
  6882. else
  6883. rIxpResult.pszResponse = PszDupA(pszDetails);
  6884. rIxpResult.uiServerError = 0;
  6885. rIxpResult.hrServerError = S_OK;
  6886. rIxpResult.dwSocketError = m_pSocket->GetLastError();
  6887. rIxpResult.pszProblem = PszDupA(pszFailureText);
  6888. // Suspend watchdog during callback
  6889. LeaveBusy();
  6890. // Log it
  6891. if (m_pLogFile) {
  6892. int iLengthOfSz;
  6893. char sz[64];
  6894. LPSTR pszErrorText;
  6895. CByteStream bstmErrLog;
  6896. // wnsprintf is limited to an output of 1024 bytes. Use a stream.
  6897. bstmErrLog.Write("ERROR: \"", 8, NULL); // Ignore IStream::Write errors
  6898. bstmErrLog.Write(pszFailureText, lstrlen(pszFailureText), NULL);
  6899. if (bIncludeLastResponse || NULL == pszDetails)
  6900. iLengthOfSz = wnsprintf(sz, ARRAYSIZE(sz), "\", hr=0x%lX", hrResult);
  6901. else {
  6902. bstmErrLog.Write("\" (", 3, NULL);
  6903. bstmErrLog.Write(pszDetails, lstrlen(pszDetails), NULL);
  6904. iLengthOfSz = wnsprintf(sz, ARRAYSIZE(sz), "), hr=0x%lX", hrResult);
  6905. }
  6906. bstmErrLog.Write(sz, iLengthOfSz, NULL);
  6907. if (SUCCEEDED(bstmErrLog.HrAcquireStringA(NULL, &pszErrorText, ACQ_COPY)))
  6908. m_pLogFile->WriteLog(LOGFILE_DB, pszErrorText);
  6909. }
  6910. // Give to callback
  6911. m_pCallback->OnError(m_status, &rIxpResult, THIS_IInternetTransport);
  6912. // Restore the watchdog if required
  6913. if (FALSE == m_fBusy &&
  6914. (NULL != m_piciPendingList || (NULL != m_piciCmdInSending &&
  6915. icIDLE_COMMAND != m_piciCmdInSending->icCommandID))) {
  6916. hrResult = HrEnterBusy();
  6917. Assert(SUCCEEDED(hrResult));
  6918. }
  6919. // Free duplicated strings
  6920. SafeMemFree(rIxpResult.pszResponse);
  6921. SafeMemFree(rIxpResult.pszProblem);
  6922. } // OnIMAPError
  6923. //***************************************************************************
  6924. // Function: HandsOffCallback
  6925. //
  6926. // Purpose:
  6927. // This function guarantees that the default callback handler will not be
  6928. // called from this point on, even if it has commands in the air. The pointer
  6929. // to the default CB handler is released and removed from all commands in
  6930. // the air and from the default CB handler module variable. NOTE that non-
  6931. // default CB handlers are not affected by this call.
  6932. //
  6933. // Returns:
  6934. // HRESULT indicating success or failure.
  6935. //***************************************************************************
  6936. HRESULT STDMETHODCALLTYPE CImap4Agent::HandsOffCallback(void)
  6937. {
  6938. CIMAPCmdInfo *pCurrentCmd;
  6939. // Check current status
  6940. if (NULL == m_pCBHandler) {
  6941. Assert(NULL == m_pCallback);
  6942. return S_OK; // We're already done
  6943. }
  6944. // Remove default CB handler from all cmds in send queue
  6945. // NB: No need to deal with m_piciCmdInSending, since it points into this queue
  6946. pCurrentCmd = m_piciSendQueue;
  6947. while (NULL != pCurrentCmd) {
  6948. if (pCurrentCmd->pCBHandler == m_pCBHandler) {
  6949. pCurrentCmd->pCBHandler->Release();
  6950. pCurrentCmd->pCBHandler = NULL;
  6951. }
  6952. pCurrentCmd = pCurrentCmd->piciNextCommand;
  6953. }
  6954. // Remove default CB handler from all cmds in pending command queue
  6955. pCurrentCmd = m_piciPendingList;
  6956. while (NULL != pCurrentCmd) {
  6957. if (pCurrentCmd->pCBHandler == m_pCBHandler) {
  6958. pCurrentCmd->pCBHandler->Release();
  6959. pCurrentCmd->pCBHandler = NULL;
  6960. }
  6961. pCurrentCmd = pCurrentCmd->piciNextCommand;
  6962. }
  6963. // Remove default CB handler from CImap4Agent and CIxpBase module vars
  6964. m_pCBHandler->Release();
  6965. m_pCBHandler = NULL;
  6966. m_pCallback->Release();
  6967. m_pCallback = NULL;
  6968. return S_OK;
  6969. } // HandsOffCallback
  6970. //***************************************************************************
  6971. // Function: FreeAllData
  6972. //
  6973. // Purpose:
  6974. // This function deallocates the send and receive queues, the
  6975. // MsgSeqNumToUID table, and the authentication mechanism list.
  6976. //
  6977. // Arguments:
  6978. // HRESULT hrTerminatedCmdResult [in] - if a command is found in the send
  6979. // or pending queue, we must issue a cmd completion notification. This
  6980. // argument tells us what hrResult to return. It must indicate FAILURE.
  6981. //***************************************************************************
  6982. void CImap4Agent::FreeAllData(HRESULT hrTerminatedCmdResult)
  6983. {
  6984. Assert(FAILED(hrTerminatedCmdResult)); // If cmds pending, we FAILED
  6985. char szBuf[MAX_RESOURCESTRING];
  6986. FreeAuthStatus();
  6987. // Clean up the receive queue
  6988. if (NULL != m_ilqRecvQueue.pilfFirstFragment) {
  6989. DWORD dwMsgSeqNum;
  6990. // If receive queue holds a FETCH response, and if the client has stored
  6991. // non-NULL cookies in m_fbpFetchBodyPartInProgress, notify caller that it's over
  6992. if (isFetchResponse(&m_ilqRecvQueue, &dwMsgSeqNum) &&
  6993. (NULL != m_fbpFetchBodyPartInProgress.lpFetchCookie1 ||
  6994. NULL != m_fbpFetchBodyPartInProgress.lpFetchCookie2)) {
  6995. FETCH_CMD_RESULTS_EX fetchResults;
  6996. FETCH_CMD_RESULTS fcrOldFetchStruct;
  6997. IMAP_RESPONSE irIMAPResponse;
  6998. ZeroMemory(&fetchResults, sizeof(fetchResults));
  6999. fetchResults.dwMsgSeqNum = dwMsgSeqNum;
  7000. fetchResults.lpFetchCookie1 = m_fbpFetchBodyPartInProgress.lpFetchCookie1;
  7001. fetchResults.lpFetchCookie2 = m_fbpFetchBodyPartInProgress.lpFetchCookie2;
  7002. irIMAPResponse.wParam = 0;
  7003. irIMAPResponse.lParam = 0;
  7004. irIMAPResponse.hrResult = hrTerminatedCmdResult;
  7005. irIMAPResponse.lpszResponseText = NULL; // Not relevant
  7006. if (IMAP_FETCHEX_ENABLE & m_dwFetchFlags)
  7007. {
  7008. irIMAPResponse.irtResponseType = irtUPDATE_MSG_EX;
  7009. irIMAPResponse.irdResponseData.pFetchResultsEx = &fetchResults;
  7010. }
  7011. else
  7012. {
  7013. DowngradeFetchResponse(&fcrOldFetchStruct, &fetchResults);
  7014. irIMAPResponse.irtResponseType = irtUPDATE_MSG;
  7015. irIMAPResponse.irdResponseData.pFetchResults = &fcrOldFetchStruct;
  7016. }
  7017. OnIMAPResponse(m_pCBHandler, &irIMAPResponse);
  7018. }
  7019. while (NULL != m_ilqRecvQueue.pilfFirstFragment) {
  7020. IMAP_LINE_FRAGMENT *pilf;
  7021. pilf = DequeueFragment(&m_ilqRecvQueue);
  7022. FreeFragment(&pilf);
  7023. } // while
  7024. } // if (receive queue not empty)
  7025. // To avoid deadlock, whenever we need to enter more than one CS, we must request
  7026. // them in the order specified in the CImap4Agent class definition. Any calls to
  7027. // OnIMAPResponse will require CIxpBase::m_cs, so enter that CS now.
  7028. EnterCriticalSection(&m_cs);
  7029. // Clean up the send queue
  7030. EnterCriticalSection(&m_csSendQueue);
  7031. m_piciCmdInSending = NULL; // No need to delete obj, it points into m_piciSendQueue
  7032. while (NULL != m_piciSendQueue) {
  7033. CIMAPCmdInfo *piciDeletedCmd;
  7034. // Dequeue next command in send queue
  7035. piciDeletedCmd = m_piciSendQueue;
  7036. m_piciSendQueue = piciDeletedCmd->piciNextCommand;
  7037. // Send notification except for non-user-initiated IMAP commands
  7038. if (icIDLE_COMMAND != piciDeletedCmd->icCommandID &&
  7039. icCAPABILITY_COMMAND != piciDeletedCmd->icCommandID &&
  7040. icLOGIN_COMMAND != piciDeletedCmd->icCommandID &&
  7041. icAUTHENTICATE_COMMAND != piciDeletedCmd->icCommandID) {
  7042. IMAP_RESPONSE irIMAPResponse;
  7043. // Notify caller that his command could not be completed
  7044. LoadString(g_hLocRes, idsIMAPCmdNotSent, szBuf, ARRAYSIZE(szBuf));
  7045. irIMAPResponse.wParam = piciDeletedCmd->wParam;
  7046. irIMAPResponse.lParam = piciDeletedCmd->lParam;
  7047. irIMAPResponse.hrResult = hrTerminatedCmdResult;
  7048. irIMAPResponse.lpszResponseText = szBuf;
  7049. irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
  7050. OnIMAPResponse(piciDeletedCmd->pCBHandler, &irIMAPResponse);
  7051. }
  7052. delete piciDeletedCmd;
  7053. } // while (NULL != m_piciSendQueue)
  7054. LeaveCriticalSection(&m_csSendQueue);
  7055. // Clean up the pending command queue
  7056. EnterCriticalSection(&m_csPendingList);
  7057. while (NULL != m_piciPendingList) {
  7058. CIMAPCmdInfo *piciDeletedCmd;
  7059. IMAP_RESPONSE irIMAPResponse;
  7060. piciDeletedCmd = m_piciPendingList;
  7061. m_piciPendingList = piciDeletedCmd->piciNextCommand;
  7062. // Send notification except for non-user-initiated IMAP commands
  7063. if (icIDLE_COMMAND != piciDeletedCmd->icCommandID &&
  7064. icCAPABILITY_COMMAND != piciDeletedCmd->icCommandID &&
  7065. icLOGIN_COMMAND != piciDeletedCmd->icCommandID &&
  7066. icAUTHENTICATE_COMMAND != piciDeletedCmd->icCommandID) {
  7067. IMAP_RESPONSE irIMAPResponse;
  7068. // Notify caller that his command could not be completed
  7069. LoadString(g_hLocRes, idsIMAPCmdStillPending, szBuf, ARRAYSIZE(szBuf));
  7070. irIMAPResponse.wParam = piciDeletedCmd->wParam;
  7071. irIMAPResponse.lParam = piciDeletedCmd->lParam;
  7072. irIMAPResponse.hrResult = hrTerminatedCmdResult;
  7073. irIMAPResponse.lpszResponseText = szBuf;
  7074. irIMAPResponse.irtResponseType = irtCOMMAND_COMPLETION;
  7075. OnIMAPResponse(piciDeletedCmd->pCBHandler, &irIMAPResponse);
  7076. }
  7077. delete piciDeletedCmd;
  7078. } // while (NULL != m_piciPendingList)
  7079. LeaveCriticalSection(&m_csPendingList);
  7080. LeaveCriticalSection(&m_cs);
  7081. // Any literals in progress?
  7082. if (NULL != m_pilfLiteralInProgress) {
  7083. m_dwLiteralInProgressBytesLeft = 0;
  7084. FreeFragment(&m_pilfLiteralInProgress);
  7085. }
  7086. // Any fetch body parts in progress?
  7087. if (NULL != m_fbpFetchBodyPartInProgress.pszBodyTag)
  7088. MemFree(m_fbpFetchBodyPartInProgress.pszBodyTag);
  7089. m_fbpFetchBodyPartInProgress = FetchBodyPart_INIT; // So we don't try to free pszBodyTag twice
  7090. // Free MsgSeqNumToUID table
  7091. ResetMsgSeqNumToUID();
  7092. } // FreeAllData
  7093. //***************************************************************************
  7094. // Function: FreeAuthStatus
  7095. //
  7096. // Purpose:
  7097. // This function frees the data allocated during the course of an
  7098. // authentication (all of which is stored in m_asAuthStatus).
  7099. //***************************************************************************
  7100. void CImap4Agent::FreeAuthStatus(void)
  7101. {
  7102. int i;
  7103. // Drop the authentication mechanism list
  7104. for (i=0; i < m_asAuthStatus.iNumAuthTokens; i++) {
  7105. if (NULL != m_asAuthStatus.rgpszAuthTokens[i]) {
  7106. MemFree(m_asAuthStatus.rgpszAuthTokens[i]);
  7107. m_asAuthStatus.rgpszAuthTokens[i] = NULL;
  7108. }
  7109. }
  7110. m_asAuthStatus.iNumAuthTokens = 0;
  7111. // Free up Sicily stuff
  7112. SSPIFreeContext(&m_asAuthStatus.rSicInfo);
  7113. if (NULL != m_asAuthStatus.pPackages && 0 != m_asAuthStatus.cPackages)
  7114. SSPIFreePackages(&m_asAuthStatus.pPackages, m_asAuthStatus.cPackages);
  7115. m_asAuthStatus = AuthStatus_INIT;
  7116. } // FreeAuthStatus
  7117. //===========================================================================
  7118. // CIMAPCmdInfo Class
  7119. //===========================================================================
  7120. // This class contains information about an IMAP command, such as a queue
  7121. // of line fragments which constitute the actual command, the tag of the
  7122. // command, and the transaction ID used to identify the command to the
  7123. // CImap4Agent user.
  7124. //***************************************************************************
  7125. // Function: CIMAPCmdInfo (Constructor)
  7126. // NOTE that this function deviates from convention in that its public
  7127. // module variables are NOT prefixed with a "m_". This was done to make
  7128. // access to its public module variables more readable.
  7129. //***************************************************************************
  7130. CIMAPCmdInfo::CIMAPCmdInfo(CImap4Agent *pImap4Agent,
  7131. IMAP_COMMAND icCmd, SERVERSTATE ssMinimumStateArg,
  7132. WPARAM wParamArg, LPARAM lParamArg,
  7133. IIMAPCallback *pCBHandlerArg)
  7134. {
  7135. Assert(NULL != pImap4Agent);
  7136. Assert(icNO_COMMAND != icCmd);
  7137. // Set module (that's right, module) variables
  7138. icCommandID = icCmd;
  7139. ssMinimumState = ssMinimumStateArg;
  7140. wParam = wParamArg;
  7141. lParam = lParamArg;
  7142. // Set ptr to CB Handler - if argument is NULL, substitute default CB handler
  7143. if (NULL != pCBHandlerArg)
  7144. pCBHandler = pCBHandlerArg;
  7145. else
  7146. pCBHandler = pImap4Agent->m_pCBHandler;
  7147. Assert(NULL != pCBHandler)
  7148. if (NULL != pCBHandler)
  7149. pCBHandler->AddRef();
  7150. // No AddRef() necessary, since CImap4Agent is our sole user. When they
  7151. // go, we go, and so does our pointer.
  7152. m_pImap4Agent = pImap4Agent;
  7153. pImap4Agent->GenerateCommandTag(szTag);
  7154. pilqCmdLineQueue = new IMAP_LINEFRAG_QUEUE;
  7155. *pilqCmdLineQueue = ImapLinefragQueue_INIT;
  7156. fUIDRangeList = FALSE;
  7157. piciNextCommand = NULL;
  7158. } // CIMAPCmdInfo
  7159. //***************************************************************************
  7160. // Function: ~CIMAPCmdInfo (Destructor)
  7161. //***************************************************************************
  7162. CIMAPCmdInfo::~CIMAPCmdInfo(void)
  7163. {
  7164. // Flush any unsent items from the command line queue
  7165. while (NULL != pilqCmdLineQueue->pilfFirstFragment) {
  7166. IMAP_LINE_FRAGMENT *pilf;
  7167. pilf = m_pImap4Agent->DequeueFragment(pilqCmdLineQueue);
  7168. m_pImap4Agent->FreeFragment(&pilf);
  7169. }
  7170. delete pilqCmdLineQueue;
  7171. if (NULL != pCBHandler)
  7172. pCBHandler->Release();
  7173. } // ~CIMAPCmdInfo
  7174. //===========================================================================
  7175. // Message Sequence Number to UID Conversion Code
  7176. //===========================================================================
  7177. //***************************************************************************
  7178. // Function: ResizeMsgSeqNumTable
  7179. //
  7180. // Purpose:
  7181. // This function is called whenever we receive an EXISTS response. It
  7182. // resizes the MsgSeqNumToUID table to match the current size of the mailbox.
  7183. //
  7184. // Arguments:
  7185. // DWORD dwSizeOfMbox [in] - the number returned via the EXISTS response.
  7186. //
  7187. // Returns:
  7188. // HRESULT indicating success or failure.
  7189. //***************************************************************************
  7190. HRESULT STDMETHODCALLTYPE CImap4Agent::ResizeMsgSeqNumTable(DWORD dwSizeOfMbox)
  7191. {
  7192. BOOL bResult;
  7193. Assert(m_lRefCount > 0);
  7194. if (dwSizeOfMbox == m_dwSizeOfMsgSeqNumToUID)
  7195. return S_OK; // Nothing to do, table is already of correct size
  7196. // Check for the case where EXISTS reports new mailbox size before we
  7197. // receive the EXPUNGE cmds to notify us of deletions
  7198. if (dwSizeOfMbox < m_dwHighestMsgSeqNum) {
  7199. // Bad, bad server! (Although not strictly prohibited)
  7200. AssertSz(FALSE, "Received EXISTS before EXPUNGE commands! Check your server.");
  7201. return S_OK; // We only resize after all EXPUNGE responses have been received,
  7202. // since we don't know who to delete and since the svr expects us to
  7203. // use OLD msg seq nums until it can update us with EXPUNGE responses
  7204. // Return S_OK since this is non-fatal.
  7205. }
  7206. // Check for the case where the mailbox has become empty (MemRealloc's not as flex as realloc)
  7207. if (0 == dwSizeOfMbox) {
  7208. ResetMsgSeqNumToUID();
  7209. return S_OK;
  7210. }
  7211. // Resize the table
  7212. bResult = MemRealloc((void **)&m_pdwMsgSeqNumToUID, dwSizeOfMbox * sizeof(DWORD));
  7213. if (FALSE == bResult) {
  7214. char szTemp[MAX_RESOURCESTRING];
  7215. // Report out-of-memory error
  7216. LoadString(g_hLocRes, idsMemory, szTemp, sizeof(szTemp));
  7217. OnIMAPError(E_OUTOFMEMORY, szTemp, DONT_USE_LAST_RESPONSE);
  7218. ResetMsgSeqNumToUID();
  7219. return E_OUTOFMEMORY;
  7220. }
  7221. else {
  7222. LONG lSizeOfUninitMemory;
  7223. // Zero any memory above m_dwHighestMsgSeqNum element to end of array
  7224. lSizeOfUninitMemory = (dwSizeOfMbox - m_dwHighestMsgSeqNum) * sizeof(DWORD);
  7225. if (0 < lSizeOfUninitMemory)
  7226. ZeroMemory(m_pdwMsgSeqNumToUID + m_dwHighestMsgSeqNum, lSizeOfUninitMemory);
  7227. m_dwSizeOfMsgSeqNumToUID = dwSizeOfMbox;
  7228. }
  7229. // Make sure we never shrink the table smaller than highest msg seq num
  7230. Assert(m_dwHighestMsgSeqNum <= m_dwSizeOfMsgSeqNumToUID);
  7231. return S_OK;
  7232. } // ResizeMsgSeqNumTable
  7233. //***************************************************************************
  7234. // Function: UpdateSeqNumToUID
  7235. //
  7236. // Purpose:
  7237. // This function is called whenever we receive a FETCH response which has
  7238. // both a message sequence number and a UID number. It updates the
  7239. // MsgSeqNumToUID table so that given msg seq number maps to the given UID.
  7240. //
  7241. // Arguments:
  7242. // DWORD dwMsgSeqNum [in] - the message sequence number of the FETCH
  7243. // response.
  7244. // DWORD dwUID [in] - the UID of the given message sequence number.
  7245. //
  7246. // Returns:
  7247. // HRESULT indicating success or failure.
  7248. //***************************************************************************
  7249. HRESULT STDMETHODCALLTYPE CImap4Agent::UpdateSeqNumToUID(DWORD dwMsgSeqNum, DWORD dwUID)
  7250. {
  7251. Assert(m_lRefCount > 0);
  7252. // Check args
  7253. if (0 == dwMsgSeqNum || 0 == dwUID) {
  7254. AssertSz(FALSE, "Zero is not an acceptable number for a msg seq num or UID.");
  7255. return E_INVALIDARG;
  7256. }
  7257. // Check if we have a table
  7258. if (NULL == m_pdwMsgSeqNumToUID) {
  7259. // This could mean programmer error, or server never gave us EXISTS
  7260. DOUT("You're trying to update a non-existent MsgSeqNumToUID table.");
  7261. }
  7262. // We cannot check against m_dwHighestMsgSeqNum, because we update that
  7263. // variable at the end of this function! The second-best thing to do is
  7264. // to verify that we lie within m_dwSizeOfMsgSeqNum.
  7265. if (dwMsgSeqNum > m_dwSizeOfMsgSeqNumToUID || NULL == m_pdwMsgSeqNumToUID) {
  7266. HRESULT hrResult;
  7267. DOUT("Msg seq num out of range! Could be server bug, or out of memory.");
  7268. hrResult = ResizeMsgSeqNumTable(dwMsgSeqNum); // Do the robust thing: resize our table
  7269. if(FAILED(hrResult))
  7270. return hrResult;
  7271. }
  7272. // Check for screwups
  7273. // First check if a UID has been changed
  7274. if (0 != m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] &&
  7275. m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] != dwUID) {
  7276. char szTemp[MAX_RESOURCESTRING];
  7277. char szDetails[MAX_RESOURCESTRING];
  7278. wnsprintf(szDetails, ARRAYSIZE(szDetails), "MsgSeqNum %lu: Previous UID: %lu, New UID: %lu.",
  7279. dwMsgSeqNum, m_pdwMsgSeqNumToUID[dwMsgSeqNum-1], dwUID);
  7280. LoadString(g_hLocRes, idsIMAPUIDChanged, szTemp, sizeof(szTemp));
  7281. OnIMAPError(IXP_E_IMAP_CHANGEDUID, szTemp, DONT_USE_LAST_RESPONSE, szDetails);
  7282. // In this case, we'll still return S_OK, but user will know of problem
  7283. }
  7284. // Next, verify that this UID is strictly ascending: this UID should be
  7285. // strictly greater than previous UID, and stricly less than succeeding UID
  7286. // Succeeding UID can be 0 (indicates it's uninitialized)
  7287. if (1 != dwMsgSeqNum && m_pdwMsgSeqNumToUID[dwMsgSeqNum-2] >= dwUID || // Check UID below
  7288. dwMsgSeqNum < m_dwSizeOfMsgSeqNumToUID && // Check UID above
  7289. 0 != m_pdwMsgSeqNumToUID[dwMsgSeqNum] &&
  7290. m_pdwMsgSeqNumToUID[dwMsgSeqNum] <= dwUID) {
  7291. char szTemp[MAX_RESOURCESTRING];
  7292. char szDetails[MAX_RESOURCESTRING];
  7293. wnsprintf(szDetails, ARRAYSIZE(szDetails), "MsgSeqNum %lu, New UID %lu. Prev UID: %lu, Next UID: %lu.",
  7294. dwMsgSeqNum, dwUID, 1 == dwMsgSeqNum ? 0 : m_pdwMsgSeqNumToUID[dwMsgSeqNum-2],
  7295. dwMsgSeqNum >= m_dwSizeOfMsgSeqNumToUID ? 0 : m_pdwMsgSeqNumToUID[dwMsgSeqNum]);
  7296. LoadString(g_hLocRes, idsIMAPUIDOrder, szTemp, sizeof(szTemp));
  7297. OnIMAPError(IXP_E_IMAP_UIDORDER, szTemp, DONT_USE_LAST_RESPONSE, szDetails);
  7298. // In this case, we'll still return S_OK, but user will know of problem
  7299. }
  7300. // Record the given UID under the given msg seq number
  7301. m_pdwMsgSeqNumToUID[dwMsgSeqNum-1] = dwUID;
  7302. if (dwMsgSeqNum > m_dwHighestMsgSeqNum)
  7303. m_dwHighestMsgSeqNum = dwMsgSeqNum;
  7304. return S_OK;
  7305. } // UpdateSeqNumToUID
  7306. //***************************************************************************
  7307. // Function: RemoveSequenceNum
  7308. //
  7309. // Purpose:
  7310. // This function is called whenever we receive an EXPUNGE response. It
  7311. // removes the given message sequence number from the MsgSeqNumToUID table,
  7312. // and compacts the table so that all message sequence numbers following
  7313. // the deleted one are re-sequenced.
  7314. //
  7315. // Arguments:
  7316. // DWORD dwDeletedMsgSeqNum [in] - message sequence number of deleted msg.
  7317. //
  7318. // Returns:
  7319. // HRESULT indicating success or failure.
  7320. //***************************************************************************
  7321. HRESULT STDMETHODCALLTYPE CImap4Agent::RemoveSequenceNum(DWORD dwDeletedMsgSeqNum)
  7322. {
  7323. DWORD *pdwDest, *pdwSrc;
  7324. LONG lSizeOfBlock;
  7325. Assert(m_lRefCount > 0);
  7326. // Check arguments
  7327. if (dwDeletedMsgSeqNum > m_dwHighestMsgSeqNum || 0 == dwDeletedMsgSeqNum) {
  7328. AssertSz(FALSE, "Msg seq num out of range! Could be server bug, or out of memory.");
  7329. return E_FAIL;
  7330. }
  7331. // Check if we have a table
  7332. if (NULL == m_pdwMsgSeqNumToUID) {
  7333. // This could mean programmer error, or server never gave us EXISTS
  7334. AssertSz(FALSE, "You're trying to update a non-existent MsgSeqNumToUID table.");
  7335. return E_FAIL;
  7336. }
  7337. // Compact the array
  7338. pdwDest = &m_pdwMsgSeqNumToUID[dwDeletedMsgSeqNum-1];
  7339. pdwSrc = pdwDest + 1;
  7340. lSizeOfBlock = (m_dwHighestMsgSeqNum - dwDeletedMsgSeqNum) * sizeof(DWORD);
  7341. if (0 < lSizeOfBlock)
  7342. MoveMemory(pdwDest, pdwSrc, lSizeOfBlock);
  7343. m_dwHighestMsgSeqNum -= 1;
  7344. // Initialize the empty element at top of array to prevent confusion
  7345. ZeroMemory(m_pdwMsgSeqNumToUID + m_dwHighestMsgSeqNum, sizeof(DWORD));
  7346. return S_OK;
  7347. } // RemoveSequenceNum
  7348. //***************************************************************************
  7349. // Function: MsgSeqNumToUID
  7350. //
  7351. // Purpose:
  7352. // This function takes a message sequence number and converts it to a UID
  7353. // based on the MsgSeqNumToUID table.
  7354. //
  7355. // Arguments:
  7356. // DWORD dwMsgSeqNum [in] - the sequence number for which the caller wants
  7357. // to know the UID.
  7358. // DWORD *pdwUID [out] - the UID associated with the given sequence number
  7359. // is returned here. If none could be found, this function returns 0.
  7360. //
  7361. // Returns:
  7362. // HRESULT indicating success or failure.
  7363. //***************************************************************************
  7364. HRESULT STDMETHODCALLTYPE CImap4Agent::MsgSeqNumToUID(DWORD dwMsgSeqNum,
  7365. DWORD *pdwUID)
  7366. {
  7367. Assert(m_lRefCount > 0);
  7368. Assert(NULL != pdwUID);
  7369. // Check arguments
  7370. if (dwMsgSeqNum > m_dwHighestMsgSeqNum || 0 == dwMsgSeqNum) {
  7371. AssertSz(FALSE, "Msg seq num out of range! Could be server bug, or out of memory.");
  7372. *pdwUID = 0;
  7373. return E_FAIL;
  7374. }
  7375. // Check if we have a table
  7376. if (NULL == m_pdwMsgSeqNumToUID) {
  7377. // This could mean programmer error, or server never gave us EXISTS
  7378. AssertSz(FALSE, "You're trying to update a non-existent MsgSeqNumToUID table.");
  7379. *pdwUID = 0;
  7380. return E_FAIL;
  7381. }
  7382. // IE5 Bug #44956: It's OK for a MsgSeqNumToUID mapping to result in a UID of 0. Sometimes an IMAP
  7383. // server can skip a range of messages. In such cases we will return a failure result.
  7384. *pdwUID = m_pdwMsgSeqNumToUID[dwMsgSeqNum-1];
  7385. if (0 == *pdwUID)
  7386. return OLE_E_BLANK;
  7387. else
  7388. return S_OK;
  7389. } // MsgSeqNumToUID
  7390. //***************************************************************************
  7391. // Function: GetMsgSeqNumToUIDArray
  7392. //
  7393. // Purpose:
  7394. // This function returns a copy of the MsgSeqNumToUID array. The caller
  7395. // will want to do this to delete messages from the cache which no longer
  7396. // exist on the server, for example.
  7397. //
  7398. // Arguments:
  7399. // DWORD **ppdwMsgSeqNumToUIDArray [out] - the function returns a pointer
  7400. // to the copy of the MsgSeqNumToUID array in this argument. Note that
  7401. // it is the caller's responsibility to MemFree the array. If no array
  7402. // is available, or it is empty, the returned pointer value is NULL.
  7403. // DWORD *pdwNumberOfElements [out] - the function returns the size of
  7404. // the MsgSeqNumToUID array.
  7405. //
  7406. // Returns:
  7407. // HRESULT indicating success or failure.
  7408. //***************************************************************************
  7409. HRESULT STDMETHODCALLTYPE CImap4Agent::GetMsgSeqNumToUIDArray(DWORD **ppdwMsgSeqNumToUIDArray,
  7410. DWORD *pdwNumberOfElements)
  7411. {
  7412. BOOL bResult;
  7413. DWORD dwSizeOfArray;
  7414. Assert(m_lRefCount > 0);
  7415. Assert(NULL != ppdwMsgSeqNumToUIDArray);
  7416. Assert(NULL != pdwNumberOfElements);
  7417. // Check if our table is empty. If so, return success, but no array
  7418. if (NULL == m_pdwMsgSeqNumToUID || 0 == m_dwHighestMsgSeqNum) {
  7419. *ppdwMsgSeqNumToUIDArray = NULL;
  7420. *pdwNumberOfElements = 0;
  7421. return S_OK;
  7422. }
  7423. // We have a non-zero-size array to return. Make a copy of our table
  7424. dwSizeOfArray = m_dwHighestMsgSeqNum * sizeof(DWORD);
  7425. bResult = MemAlloc((void **)ppdwMsgSeqNumToUIDArray, dwSizeOfArray);
  7426. if (FALSE == bResult)
  7427. return E_OUTOFMEMORY;
  7428. CopyMemory(*ppdwMsgSeqNumToUIDArray, m_pdwMsgSeqNumToUID, dwSizeOfArray);
  7429. *pdwNumberOfElements = m_dwHighestMsgSeqNum;
  7430. return S_OK;
  7431. } // GetMsgSeqNumToUIDArray
  7432. //***************************************************************************
  7433. // Function: GetHighestMsgSeqNum
  7434. //
  7435. // Purpose:
  7436. // This function returns the highest message sequence number reported in
  7437. // the MsgSeqNumToUID array.
  7438. //
  7439. // Arguments:
  7440. // DWORD *pdwHighestMSN [out] - the highest message sequence number in the
  7441. // table is returned here.
  7442. //
  7443. // Returns:
  7444. // HRESULT indicating success or failure.
  7445. //***************************************************************************
  7446. HRESULT STDMETHODCALLTYPE CImap4Agent::GetHighestMsgSeqNum(DWORD *pdwHighestMSN)
  7447. {
  7448. Assert(m_lRefCount > 0);
  7449. Assert(NULL != pdwHighestMSN);
  7450. *pdwHighestMSN = m_dwHighestMsgSeqNum;
  7451. return S_OK;
  7452. } // GetHighestMsgSeqNum
  7453. //***************************************************************************
  7454. // Function: ResetMsgSeqNumToUID
  7455. //
  7456. // Purpose:
  7457. // This function resets the variables used to maintain the MsgSeqNumToUID
  7458. // table. This function is called whenever the MsgSeqNumToUID table becomes
  7459. // invalid (say, when a new mailbox is selected, or we are disconnected).
  7460. //
  7461. // Returns:
  7462. // S_OK. This function cannot fail.
  7463. //***************************************************************************
  7464. HRESULT STDMETHODCALLTYPE CImap4Agent::ResetMsgSeqNumToUID(void)
  7465. {
  7466. if (NULL != m_pdwMsgSeqNumToUID) {
  7467. MemFree(m_pdwMsgSeqNumToUID);
  7468. m_pdwMsgSeqNumToUID = NULL;
  7469. }
  7470. m_dwSizeOfMsgSeqNumToUID = 0;
  7471. m_dwHighestMsgSeqNum = 0;
  7472. return S_OK;
  7473. } // ResetMsgSeqNumToUID
  7474. //***************************************************************************
  7475. // Function: isPrintableUSASCII
  7476. //
  7477. // Purpose:
  7478. // This function determines whether the given character is directly
  7479. // encodable, or whether the character must be encoded in modified IMAP UTF7,
  7480. // as outlined in RFC2060.
  7481. //
  7482. // Arguments:
  7483. // BOOL fUnicode [in] - TRUE if input string is Unicode, otherwise FALSE.
  7484. // LPCSTR pszIn [in] - pointer to char we want to verify.
  7485. //
  7486. // Returns:
  7487. // TRUE if the given character may be directly encoded. FALSE if the
  7488. // character must be encoded in UTF-7.
  7489. //***************************************************************************
  7490. inline boolean CImap4Agent::isPrintableUSASCII(BOOL fUnicode, LPCSTR pszIn)
  7491. {
  7492. WCHAR wc;
  7493. if (fUnicode)
  7494. wc = *((LPWSTR)pszIn);
  7495. else
  7496. wc = (*pszIn & 0x00FF);
  7497. if (wc >= 0x0020 && wc <= 0x0025 ||
  7498. wc >= 0x0027 && wc <= 0x007e)
  7499. return TRUE;
  7500. else
  7501. return FALSE;
  7502. } // isPrintableUSASCII
  7503. //***************************************************************************
  7504. // Function: isIMAPModifiedBase64
  7505. //
  7506. // Purpose:
  7507. // This function determines whether the given character is in the modified
  7508. // IMAP Base64 set as defined by RFC1521, RFC1642 and RFC2060. This modified
  7509. // IMAP Base64 set is used in IMAP-modified UTF-7 encoding of mailbox names.
  7510. //
  7511. // Arguments:
  7512. // char c [in] - character to be classified.
  7513. //
  7514. // Returns:
  7515. // TRUE if given character is in the modified IMAP Base64 set, otherwise
  7516. // FALSE.
  7517. //***************************************************************************
  7518. inline boolean CImap4Agent::isIMAPModifiedBase64(const char c)
  7519. {
  7520. if (c >= 'A' && c <= 'Z' ||
  7521. c >= 'a' && c <= 'z' ||
  7522. c >= '0' && c <= '9' ||
  7523. '+' == c || ',' == c)
  7524. return TRUE;
  7525. else
  7526. return FALSE;
  7527. } // isIMAPModifiedBase64
  7528. //***************************************************************************
  7529. // Function: isEqualUSASCII
  7530. //
  7531. // Purpose:
  7532. // This function determines whether the given pointer points to the given
  7533. // USASCII character, based on whether we are in Unicode mode or not.
  7534. //
  7535. // Arguments:
  7536. // BOOL fUnicode [in] - TRUE if input string is Unicode, otherwise FALSE.
  7537. // LPSTR pszIn [in] - pointer to char we want to verify.
  7538. // char c [in] - the USASCII character we want to detect.
  7539. //
  7540. // Returns:
  7541. // TRUE if given character is the null terminator, otherwise, FALSE.
  7542. //***************************************************************************
  7543. inline boolean CImap4Agent::isEqualUSASCII(BOOL fUnicode, LPCSTR pszIn, const char c)
  7544. {
  7545. if (fUnicode) {
  7546. WCHAR wc = c & 0x00FF;
  7547. if (wc == *((LPWSTR)pszIn))
  7548. return TRUE;
  7549. else
  7550. return FALSE;
  7551. }
  7552. else {
  7553. if (c == *pszIn)
  7554. return TRUE;
  7555. else
  7556. return FALSE;
  7557. }
  7558. }
  7559. //***************************************************************************
  7560. // Function: SetUSASCIIChar
  7561. //
  7562. // Purpose:
  7563. // This function writes a USASCII character to the given string pointer.
  7564. // The purpose of this function is to allow the caller to ignore whether
  7565. // he is writing to a Unicode output or not.
  7566. //
  7567. // Arguments:
  7568. // BOOL fUnicode [in] - TRUE if target is Unicode, else FALSE.
  7569. // LPSTR pszOut [in] - pointer to character's destination. If fUnicode is
  7570. // TRUE, then two bytes will be written to this location.
  7571. // char cUSASCII [in] - the character to be written to pszOut.
  7572. //***************************************************************************
  7573. inline void CImap4Agent::SetUSASCIIChar(BOOL fUnicode, LPSTR pszOut, char cUSASCII)
  7574. {
  7575. Assert(0 == (cUSASCII & 0x80));
  7576. if (fUnicode)
  7577. {
  7578. *((LPWSTR) pszOut) = cUSASCII;
  7579. Assert(0 == (*((LPWSTR) pszOut) & 0xFF80));
  7580. }
  7581. else
  7582. *pszOut = cUSASCII;
  7583. } // SetUSASCIIChar
  7584. //***************************************************************************
  7585. // Function: MultiByteToModifiedUTF7
  7586. //
  7587. // Purpose:
  7588. // This function takes a MultiByte string and converts it to modified IMAP
  7589. // UTF7, which is described in RFC2060.
  7590. //
  7591. // Arguments:
  7592. // LPCSTR pszSource [in] - pointer to the MultiByte string to convert to UTF7.
  7593. // LPSTR *ppszDestination [out] - a pointer to a string buffer containing
  7594. // the UTF7 equivalent of pszSource is returned here. It is the caller's
  7595. // responsibility to MemFree this string.
  7596. // UINT uiSourceCP [in] - indicates the codepage for pszSource.
  7597. // DWORD dwFlags [in] - Reserved. Leave as 0.
  7598. //
  7599. // Returns:
  7600. // HRESULT indicating success or failure.
  7601. //***************************************************************************
  7602. HRESULT STDMETHODCALLTYPE CImap4Agent::MultiByteToModifiedUTF7(LPCSTR pszSource,
  7603. LPSTR *ppszDestination,
  7604. UINT uiSourceCP,
  7605. DWORD dwFlags)
  7606. {
  7607. int iResult;
  7608. HRESULT hrResult;
  7609. BOOL fPassThrough, fSkipByte;
  7610. LPCSTR pszIn, pszStartOfLastRun;
  7611. CByteStream bstmDestination;
  7612. BOOL fUnicode;
  7613. Assert(m_lRefCount > 0);
  7614. Assert(NULL != pszSource);
  7615. Assert(NULL != ppszDestination);
  7616. Assert(NULL != m_pInternational);
  7617. // Initialize variables
  7618. hrResult = S_OK;
  7619. fPassThrough = TRUE;
  7620. fSkipByte = FALSE;
  7621. pszIn = pszSource;
  7622. pszStartOfLastRun = pszSource;
  7623. fUnicode = (CP_UNICODE == uiSourceCP);
  7624. *ppszDestination = NULL;
  7625. // Loop through the entire input str either in one of two modes:
  7626. // Passthrough, or non-US string collection (where we determine
  7627. // the length of a string which must be encoded in UTF-7).
  7628. while (1) {
  7629. // Skip over the trail bytes
  7630. if (fSkipByte) {
  7631. AssertSz(FALSE == fUnicode, "Unicode has no trail bytes");
  7632. fSkipByte = FALSE;
  7633. if ('\0' != *pszIn)
  7634. pszIn += 1;
  7635. continue;
  7636. }
  7637. if (fPassThrough) {
  7638. if (isEqualUSASCII(fUnicode, pszIn, '&') || isEqualUSASCII(fUnicode, pszIn, '\0') ||
  7639. FALSE == isPrintableUSASCII(fUnicode, pszIn)) {
  7640. // Flush USASCII characters collected until now (if any)
  7641. if (pszIn - pszStartOfLastRun > 0) {
  7642. LPSTR pszFreeMe = NULL;
  7643. LPCSTR pszUSASCII;
  7644. DWORD dwUSASCIILen = 0;
  7645. if (fUnicode) {
  7646. hrResult = UnicodeToUSASCII(&pszFreeMe, (LPCWSTR) pszStartOfLastRun,
  7647. (DWORD) (pszIn - pszStartOfLastRun), &dwUSASCIILen);
  7648. if (FAILED(hrResult))
  7649. goto exit;
  7650. pszUSASCII = pszFreeMe;
  7651. }
  7652. else {
  7653. pszUSASCII = pszStartOfLastRun;
  7654. dwUSASCIILen = (DWORD) (pszIn - pszStartOfLastRun);
  7655. }
  7656. hrResult = bstmDestination.Write(pszUSASCII, dwUSASCIILen, NULL);
  7657. if (NULL != pszFreeMe)
  7658. MemFree(pszFreeMe);
  7659. if (FAILED(hrResult))
  7660. goto exit;
  7661. }
  7662. // Special-case the '&' character: it is converted to "&-"
  7663. if (isEqualUSASCII(fUnicode, pszIn, '&')) {
  7664. // Write "&-" to stream (always in USASCII)
  7665. hrResult = bstmDestination.Write("&-", sizeof("&-") - 1, NULL);
  7666. if (FAILED(hrResult))
  7667. goto exit;
  7668. // Reset pointers
  7669. pszStartOfLastRun = pszIn + (fUnicode ? 2 : 1); // Point past "&"
  7670. } // if ('&' == cCurrent)
  7671. else if (FALSE == isEqualUSASCII(fUnicode, pszIn, '\0')) {
  7672. Assert(FALSE == isPrintableUSASCII(fUnicode, pszIn));
  7673. // State transition: time for some UTF-7 encoding
  7674. fPassThrough = FALSE;
  7675. pszStartOfLastRun = pszIn;
  7676. if (FALSE == fUnicode && IsDBCSLeadByteEx(uiSourceCP, *pszIn))
  7677. fSkipByte = TRUE;
  7678. } // else if ('\0' != cCurrent): shortcut calc for non-printable USASCII
  7679. } // if ('&' == cCurrent || '\0' == cCurrent || FALSE == isPrintableUSASCII(cCurrent))
  7680. // Otherwise do nothing, we're collecting a run of USASCII chars
  7681. } // if (fPassThrough)
  7682. else {
  7683. // Non-US String Collection: Keep advancing through input str until
  7684. // we find a char which does not need to be encoded in UTF-7 (incl. NULL)
  7685. if (isPrintableUSASCII(fUnicode, pszIn) || isEqualUSASCII(fUnicode, pszIn, '&') ||
  7686. isEqualUSASCII(fUnicode, pszIn, '\0')) {
  7687. LPSTR pszOut = NULL;
  7688. int iNumCharsWritten;
  7689. // State transition: back to passthrough mode
  7690. fPassThrough = TRUE;
  7691. // Convert non-US string to UTF-7
  7692. hrResult = NonUSStringToModifiedUTF7(uiSourceCP, pszStartOfLastRun,
  7693. (DWORD) (pszIn - pszStartOfLastRun), &pszOut, &iNumCharsWritten);
  7694. if (FAILED(hrResult))
  7695. goto exit;
  7696. // Write modified UTF-7 string to stream
  7697. hrResult = bstmDestination.Write(pszOut, iNumCharsWritten, NULL);
  7698. MemFree(pszOut);
  7699. if (FAILED(hrResult))
  7700. goto exit;
  7701. pszStartOfLastRun = pszIn; // Reset for USASCII collection process
  7702. continue; // Do not advance ptr: we want current char to pass through
  7703. }
  7704. else if (FALSE == fUnicode && IsDBCSLeadByteEx(uiSourceCP, *pszIn))
  7705. fSkipByte = TRUE;
  7706. } // else-NOT-fPassThrough
  7707. // Check for end-of-input
  7708. if (isEqualUSASCII(fUnicode, pszIn, '\0'))
  7709. break; // We're done here
  7710. // Advance pointer to next character
  7711. pszIn += (fUnicode ? 2 : 1);
  7712. } // while
  7713. exit:
  7714. if (SUCCEEDED(hrResult)) {
  7715. hrResult = bstmDestination.HrAcquireStringA(NULL, ppszDestination,
  7716. ACQ_DISPLACE);
  7717. if (SUCCEEDED(hrResult))
  7718. hrResult = S_OK;
  7719. }
  7720. if (NULL == *ppszDestination && SUCCEEDED(hrResult))
  7721. hrResult = E_OUTOFMEMORY;
  7722. return hrResult;
  7723. } // MultiByteToModifiedUTF7
  7724. //***************************************************************************
  7725. // Function: NonUSStringToModifiedUTF7
  7726. //
  7727. // Purpose:
  7728. // This function takes a string consisting of non-US-ASCII characters, and
  7729. // converts them to modified IMAP UTF-7 (described in RFC2060).
  7730. //
  7731. // Arguments:
  7732. // UINT uiCurrentACP [in] - codepage used to interpret pszStartOfNonUSASCII
  7733. // LPCSTR pszStartOfNonUSASCII [in] - string to convert to modified IMAP
  7734. // UTF-7.
  7735. // int iLengthOfNonUSASCII [in] - the number of characters in
  7736. // pszStartofNonUSASCII.
  7737. // LPSTR *ppszOut [out] - the destination for the modified IMAP UTF-7 version
  7738. // of pszStartOfNonUSASCII. This function appends a null-terminator. It is
  7739. // the caller's responsibility to call MemFree when finished with the buffer.
  7740. // LPINT piNumCharsWritten [out] - This function returns the number
  7741. // of characters written (excluding null-terminator) to *ppszOut.
  7742. //
  7743. // Returns:
  7744. // HRESULT indicating success or failure.
  7745. //***************************************************************************
  7746. HRESULT CImap4Agent::NonUSStringToModifiedUTF7(UINT uiCurrentACP,
  7747. LPCSTR pszStartOfNonUSASCII,
  7748. int iLengthOfNonUSASCII,
  7749. LPSTR *ppszOut,
  7750. LPINT piNumCharsWritten)
  7751. {
  7752. HRESULT hrResult;
  7753. int iNumCharsWritten, i;
  7754. LPSTR p;
  7755. BOOL fResult;
  7756. Assert(NULL != ppszOut);
  7757. // Initialize return values
  7758. *ppszOut = NULL;
  7759. *piNumCharsWritten = 0;
  7760. // First, convert the non-US string to standard UTF-7 (alloc 1 extra char: leave room for '-')
  7761. iNumCharsWritten = 0; // Tell ConvertString to find proper output buffer size
  7762. hrResult = ConvertString(uiCurrentACP, CP_UTF7, pszStartOfNonUSASCII,
  7763. &iLengthOfNonUSASCII, ppszOut, &iNumCharsWritten, sizeof(char));
  7764. if (FAILED(hrResult))
  7765. goto exit;
  7766. // Now, convert standard UTF-7 to IMAP4 modified UTF-7
  7767. // Replace leading '+' with '&'. Since under IMAP UTF-7 '+' is never
  7768. // encoded, we never expect "+-" as the result. Remember output is always USASCII
  7769. if (iNumCharsWritten > 0 && '+' == **ppszOut)
  7770. **ppszOut = '&';
  7771. else {
  7772. AssertSz(FALSE, "MLANG crapped out on me.");
  7773. hrResult = E_FAIL;
  7774. goto exit;
  7775. }
  7776. // Replace all occurrances of '/' with ','
  7777. p = *ppszOut;
  7778. for (i = 0; i < iNumCharsWritten; i++) {
  7779. if ('/' == *p)
  7780. *p = ',';
  7781. p += 1;
  7782. }
  7783. // p now points to where null-terminator should go.
  7784. // Ensure that the UTF-7 string ends with '-'. Otherwise, put one there
  7785. // (we allocated enough room for one more char plus null-term).
  7786. if ('-' != *(p-1)) {
  7787. *p = '-';
  7788. p += 1;
  7789. iNumCharsWritten += 1;
  7790. }
  7791. // Null-terminate output string, and return values
  7792. *p = '\0';
  7793. *piNumCharsWritten = iNumCharsWritten;
  7794. exit:
  7795. if (FAILED(hrResult) && NULL != *ppszOut) {
  7796. MemFree(*ppszOut);
  7797. *ppszOut = NULL;
  7798. }
  7799. return hrResult;
  7800. } // NonUSStringToModifiedUTF7
  7801. //***************************************************************************
  7802. // Function: ModifiedUTF7ToMultiByte
  7803. //
  7804. // Purpose:
  7805. // This function takes a modified IMAP UTF-7 string (as defined in RFC2060)
  7806. // and converts it to a multi-byte string.
  7807. //
  7808. // Arguments:
  7809. // LPCSTR pszSource [in] - a null-terminated string containing the modified
  7810. // IMAP UTF-7 string to convert to multibyte.
  7811. // LPSTR *ppszDestination [out] - this function returns a pointer to the
  7812. // null-terminated multibyte string (in the system codepage) obtained
  7813. // from pszSource. It is the caller's responsiblity to MemFree this
  7814. // string when it is done with it.
  7815. // UINT uiDestintationCP [in] - indicates the desired codepage for the
  7816. // destination string.
  7817. // DWORD dwFlags [in] - Reserved. Leave as 0.
  7818. //
  7819. // Returns:
  7820. // HRESULT indicating success or failure. Success result codes include:
  7821. // S_OK - pszSource successfully converted to modified UTF-7
  7822. // IXP_S_IMAP_VERBATIM_MBOX - pszSource could not be converted to multibyte,
  7823. // so ppszDestination contains a duplicate of pszSource. If target CP
  7824. // is Unicode, pszSource is converted to Unicode with the assumption
  7825. // that it is USASCII. IMAP_MBOXXLATE_VERBATIMOK must have been set via
  7826. // SetDefaultCP in order to get this behaviour.
  7827. //***************************************************************************
  7828. HRESULT STDMETHODCALLTYPE CImap4Agent::ModifiedUTF7ToMultiByte(LPCSTR pszSource,
  7829. LPSTR *ppszDestination,
  7830. UINT uiDestinationCP,
  7831. DWORD dwFlags)
  7832. {
  7833. HRESULT hrResult;
  7834. BOOL fPassThrough, fTrailByte;
  7835. LPCSTR pszIn, pszStartOfLastRun;
  7836. CByteStream bstmDestination;
  7837. BOOL fUnicode;
  7838. // Initialize variables
  7839. hrResult = S_OK;
  7840. fPassThrough = TRUE;
  7841. fTrailByte = FALSE;
  7842. pszIn = pszSource;
  7843. pszStartOfLastRun = pszSource;
  7844. fUnicode = (CP_UNICODE == uiDestinationCP);
  7845. *ppszDestination = NULL;
  7846. // Loop through the entire input str either in one of two modes:
  7847. // Passthrough, or UTF-7 string collection (where we determine
  7848. // the length of a string which was encoded in UTF-7).
  7849. while (1) {
  7850. char cCurrent;
  7851. cCurrent = *pszIn;
  7852. if (fPassThrough) {
  7853. if ((FALSE == fTrailByte && '&' == cCurrent) || '\0' == cCurrent) {
  7854. // State transition: flush collected non-UTF7
  7855. BOOL fResult;
  7856. LPSTR pszFreeMe = NULL;
  7857. LPCSTR pszNonUTF7;
  7858. int iNonUTF7Len;
  7859. int iSrcLen;
  7860. if (fUnicode) {
  7861. // Convert non-UTF7 to Unicode
  7862. // Convert system codepage to CP_UNICODE. We KNOW source should be strictly
  7863. // USASCII, but can't assume it because some IMAP servers don't strictly
  7864. // prohibit 8-bit mailbox names. SCARY.
  7865. iSrcLen = (int) (pszIn - pszStartOfLastRun); // Pass in size of input and output buffer
  7866. iNonUTF7Len = iSrcLen * sizeof(WCHAR) / sizeof(char); // We know max output buffer size
  7867. hrResult = ConvertString(GetACP(), uiDestinationCP, pszStartOfLastRun,
  7868. &iSrcLen, &pszFreeMe, &iNonUTF7Len, 0);
  7869. if (FAILED(hrResult))
  7870. goto exit;
  7871. pszNonUTF7 = pszFreeMe;
  7872. }
  7873. else {
  7874. pszNonUTF7 = pszStartOfLastRun;
  7875. iNonUTF7Len = (int) (pszIn - pszStartOfLastRun);
  7876. }
  7877. hrResult = bstmDestination.Write(pszNonUTF7, iNonUTF7Len, NULL);
  7878. if (NULL != pszFreeMe)
  7879. MemFree(pszFreeMe);
  7880. if (FAILED(hrResult))
  7881. goto exit;
  7882. // Start collecting UTF-7. Loop until we hit '-'
  7883. fPassThrough = FALSE;
  7884. pszStartOfLastRun = pszIn;
  7885. }
  7886. else {
  7887. // Non-UTF7 stuff is copied verbatim to the output: collect it. Assume
  7888. // source is in m_uiDefaultCP codepage. We SHOULD be able to assume
  7889. // source is USASCII only but some svrs are not strict about disallowing 8-bit
  7890. if (FALSE == fTrailByte && IsDBCSLeadByteEx(m_uiDefaultCP, cCurrent))
  7891. fTrailByte = TRUE;
  7892. else
  7893. fTrailByte = FALSE;
  7894. }
  7895. }
  7896. else {
  7897. // UTF-7 collection mode: Keep going until we hit non-UTF7 char
  7898. if (FALSE == isIMAPModifiedBase64(cCurrent)) {
  7899. int iLengthOfUTF7, iNumBytesWritten, iOutputBufSize;
  7900. LPSTR pszSrc, pszDest, p;
  7901. BOOL fResult;
  7902. // State transition, time to convert some modified UTF-7
  7903. fPassThrough = TRUE;
  7904. Assert(FALSE == fTrailByte);
  7905. // If current character is '-', absorb it (don't process it)
  7906. if ('-' == cCurrent)
  7907. pszIn += 1;
  7908. // Check for "&-" or "&(end of buffer/nonBase64)" sequence
  7909. iLengthOfUTF7 = (int) (pszIn - pszStartOfLastRun);
  7910. if (2 == iLengthOfUTF7 && '-' == cCurrent ||
  7911. 1 == iLengthOfUTF7) {
  7912. LPSTR psz;
  7913. DWORD dwLen;
  7914. Assert('&' == *pszStartOfLastRun);
  7915. if (fUnicode) {
  7916. psz = (LPSTR) L"&";
  7917. dwLen = 2;
  7918. }
  7919. else {
  7920. psz = "&";
  7921. dwLen = 1;
  7922. }
  7923. hrResult = bstmDestination.Write(psz, dwLen, NULL);
  7924. if (FAILED(hrResult))
  7925. goto exit;
  7926. pszStartOfLastRun = pszIn; // Set us up for non-UTF7 collection
  7927. continue; // Process next character normally
  7928. }
  7929. // Copy the UTF-7 sequence to a temp buffer, and
  7930. // convert modified IMAP UTF-7 to standard UTF-7
  7931. // First, duplicate the IMAP UTF-7 string so we can modify it
  7932. fResult = MemAlloc((void **)&pszSrc, iLengthOfUTF7 + 1); // Room for null-term
  7933. if (FALSE == fResult) {
  7934. hrResult = E_OUTOFMEMORY;
  7935. goto exit;
  7936. }
  7937. CopyMemory(pszSrc, pszStartOfLastRun, iLengthOfUTF7);
  7938. pszSrc[iLengthOfUTF7] = '\0';
  7939. // Next, replace leading '&' with '+'
  7940. Assert('&' == *pszSrc);
  7941. pszSrc[0] = '+';
  7942. // Next, replace all ',' with '/'
  7943. p = pszSrc + 1;
  7944. for (iNumBytesWritten = 1; iNumBytesWritten < iLengthOfUTF7;
  7945. iNumBytesWritten++) {
  7946. if (',' == *p)
  7947. *p = '/';
  7948. p += 1;
  7949. }
  7950. // Now convert the UTF-7 to target codepage
  7951. iNumBytesWritten = 0; // Tell ConvertString to find proper output buffer size
  7952. hrResult = ConvertString(CP_UTF7, uiDestinationCP, pszSrc, &iLengthOfUTF7,
  7953. &pszDest, &iNumBytesWritten, 0);
  7954. MemFree(pszSrc);
  7955. if (FAILED(hrResult))
  7956. goto exit;
  7957. // Now write the decoded string to the stream
  7958. hrResult = bstmDestination.Write(pszDest, iNumBytesWritten, NULL);
  7959. MemFree(pszDest);
  7960. if (FAILED(hrResult))
  7961. goto exit;
  7962. pszStartOfLastRun = pszIn; // Set us up for non-UTF7 collection
  7963. continue; // Do not advance pointer, we want to process current char
  7964. } // if end-of-modified-UTF7 run
  7965. } // else
  7966. // Check for end-of-input
  7967. if ('\0' == cCurrent)
  7968. break; // We're done here
  7969. // Advance input pointer to next character
  7970. pszIn += 1;
  7971. } // while
  7972. exit:
  7973. if (SUCCEEDED(hrResult)) {
  7974. hrResult = bstmDestination.HrAcquireStringA(NULL, ppszDestination,
  7975. ACQ_DISPLACE);
  7976. if (SUCCEEDED(hrResult))
  7977. hrResult = S_OK;
  7978. }
  7979. else if (IMAP_MBOXXLATE_VERBATIMOK & m_dwTranslateMboxFlags) {
  7980. // Could not convert UTF-7 to multibyte str. Provide verbatim copy of src
  7981. hrResult = HandleFailedTranslation(fUnicode, FALSE, pszSource, ppszDestination);
  7982. if (SUCCEEDED(hrResult))
  7983. hrResult = IXP_S_IMAP_VERBATIM_MBOX;
  7984. }
  7985. if (NULL == *ppszDestination && SUCCEEDED(hrResult))
  7986. hrResult = E_OUTOFMEMORY;
  7987. return hrResult;
  7988. } // ModifiedUTF7ToMultiByte
  7989. //***************************************************************************
  7990. // Function: UnicodeToUSASCII
  7991. //
  7992. // Purpose:
  7993. // This function converts a Unicode string to USASCII, allocates a buffer
  7994. // to hold the result and returns the buffer to the caller.
  7995. //
  7996. // Arguments:
  7997. // LPSTR *ppszUSASCII [out] - a pointer to a null-terminated USASCII string
  7998. // is returned here if the function is successful. It is the caller's
  7999. // responsibility to MemFree this buffer.
  8000. // LPCWSTR pwszUnicode [in] - a pointer to the Unicode string to convert.
  8001. // DWORD dwSrcLenInBytes [in] - the length of pwszUnicode in BYTES (NOT in
  8002. // wide chars!).
  8003. // LPDWORD pdwUSASCIILen [out] - the length of ppszUSASCII is returned here.
  8004. //
  8005. // Returns:
  8006. // HRESULT indicating success or failure.
  8007. //***************************************************************************
  8008. HRESULT CImap4Agent::UnicodeToUSASCII(LPSTR *ppszUSASCII, LPCWSTR pwszUnicode,
  8009. DWORD dwSrcLenInBytes, LPDWORD pdwUSASCIILen)
  8010. {
  8011. LPSTR pszOutput = NULL;
  8012. BOOL fResult;
  8013. HRESULT hrResult = S_OK;
  8014. LPCWSTR pwszIn;
  8015. LPSTR pszOut;
  8016. int iOutputBufSize;
  8017. DWORD dw;
  8018. if (NULL == pwszUnicode || NULL == ppszUSASCII) {
  8019. Assert(FALSE);
  8020. return E_INVALIDARG;
  8021. }
  8022. // Allocate the output buffer
  8023. *ppszUSASCII = NULL;
  8024. if (NULL != pdwUSASCIILen)
  8025. *pdwUSASCIILen = 0;
  8026. iOutputBufSize = (dwSrcLenInBytes/2) + 1;
  8027. fResult = MemAlloc((void **) &pszOutput, iOutputBufSize);
  8028. if (FALSE == fResult) {
  8029. hrResult = E_OUTOFMEMORY;
  8030. goto exit;
  8031. }
  8032. // Convert Unicode to ASCII
  8033. pwszIn = pwszUnicode;
  8034. pszOut = pszOutput;
  8035. for (dw = 0; dw < dwSrcLenInBytes; dw += 2) {
  8036. Assert(0 == (*pwszIn & 0xFF80));
  8037. *pszOut = (*pwszIn & 0x00FF);
  8038. pwszIn += 1;
  8039. pszOut += 1;
  8040. }
  8041. // Null-terminate the output
  8042. *pszOut = '\0';
  8043. Assert(pszOut - pszOutput + 1 == iOutputBufSize);
  8044. exit:
  8045. if (SUCCEEDED(hrResult)) {
  8046. *ppszUSASCII = pszOutput;
  8047. if (NULL != pdwUSASCIILen)
  8048. *pdwUSASCIILen = (DWORD) (pszOut - pszOutput);
  8049. }
  8050. return hrResult;
  8051. } // UnicodeToUSASCII
  8052. //***************************************************************************
  8053. // Function: ASCIIToUnicode
  8054. //
  8055. // Purpose:
  8056. // This function converts an ASCII string to Unicode, allocates a buffer
  8057. // to hold the result and returns the buffer to the caller.
  8058. //
  8059. // Arguments:
  8060. // LPWSTR *ppwszUnicode [out] - a pointer to a null-terminated Unicode string
  8061. // is returned here if the function is successful. It is the caller's
  8062. // responsibility to MemFree this buffer.
  8063. // LPCSTR pszASCII [in] - a pointer to the ASCII string to convert.
  8064. // DWORD dwSrcLen [in] - the length of pszASCII.
  8065. //
  8066. // Returns:
  8067. // HRESULT indicating success or failure.
  8068. //***************************************************************************
  8069. HRESULT CImap4Agent::ASCIIToUnicode(LPWSTR *ppwszUnicode, LPCSTR pszASCII,
  8070. DWORD dwSrcLen)
  8071. {
  8072. LPWSTR pwszOutput = NULL;
  8073. BOOL fResult;
  8074. HRESULT hrResult = S_OK;
  8075. LPCSTR pszIn;
  8076. LPWSTR pwszOut;
  8077. int iOutputBufSize;
  8078. DWORD dw;
  8079. if (NULL == ppwszUnicode || NULL == pszASCII) {
  8080. Assert(FALSE);
  8081. return E_INVALIDARG;
  8082. }
  8083. // Allocate the output buffer
  8084. *ppwszUnicode = NULL;
  8085. iOutputBufSize = (dwSrcLen + 1) * sizeof(WCHAR);
  8086. fResult = MemAlloc((void **) &pwszOutput, iOutputBufSize);
  8087. if (FALSE == fResult) {
  8088. hrResult = E_OUTOFMEMORY;
  8089. goto exit;
  8090. }
  8091. // Convert USASCII to Unicode
  8092. pszIn = pszASCII;
  8093. pwszOut = pwszOutput;
  8094. for (dw = 0; dw < dwSrcLen; dw++) {
  8095. *pwszOut = (WCHAR)*pszIn & 0x00FF;
  8096. pszIn += 1;
  8097. pwszOut += 1;
  8098. }
  8099. // Null-terminate the output
  8100. *pwszOut = L'\0';
  8101. Assert(pwszOut - pwszOutput + (int)sizeof(WCHAR) == iOutputBufSize);
  8102. exit:
  8103. if (SUCCEEDED(hrResult))
  8104. *ppwszUnicode = pwszOutput;
  8105. return hrResult;
  8106. } // ASCIIToUnicode
  8107. //***************************************************************************
  8108. // Function: _MultiByteToModifiedUTF7
  8109. //
  8110. // Purpose:
  8111. // Internal form of MultiByteToModifiedUTF7. Checks m_dwTranslateMboxFlags
  8112. // and uses m_uiDefaultCP. All other aspects are identical to
  8113. // MultiByteToModifiedUTF7.
  8114. //***************************************************************************
  8115. HRESULT CImap4Agent::_MultiByteToModifiedUTF7(LPCSTR pszSource, LPSTR *ppszDestination)
  8116. {
  8117. HRESULT hrResult;
  8118. // Check if we're doing translations
  8119. if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLE) ||
  8120. ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLEIMAP4) &&
  8121. ISFLAGCLEAR(m_dwCapabilityFlags, IMAP_CAPABILITY_IMAP4rev1)) {
  8122. // No translations! Just copy mailbox name VERBATIM
  8123. if (CP_UNICODE == m_uiDefaultCP)
  8124. *ppszDestination = (LPSTR) PszDupW((LPWSTR)pszSource);
  8125. else
  8126. *ppszDestination = PszDupA(pszSource);
  8127. if (NULL == *ppszDestination)
  8128. hrResult = E_OUTOFMEMORY;
  8129. else
  8130. hrResult = S_OK;
  8131. goto exit;
  8132. }
  8133. hrResult = MultiByteToModifiedUTF7(pszSource, ppszDestination, m_uiDefaultCP, 0);
  8134. exit:
  8135. return hrResult;
  8136. } // _MultiByteToModifiedUTF7
  8137. //***************************************************************************
  8138. // Function: _ModifiedUTF7ToMultiByte
  8139. //
  8140. // Purpose:
  8141. // Internal form of ModifiedUTF7ToMultiByte. Checks m_dwTranslateMboxFlags
  8142. // and uses m_uiDefaultCP. All other aspects are identical to
  8143. // ModifiedUTF7ToMultiByte.
  8144. //***************************************************************************
  8145. HRESULT CImap4Agent::_ModifiedUTF7ToMultiByte(LPCSTR pszSource, LPSTR *ppszDestination)
  8146. {
  8147. HRESULT hrResult = S_OK;
  8148. // Check if we're doing translations
  8149. if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLE) ||
  8150. ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_DISABLEIMAP4) &&
  8151. ISFLAGCLEAR(m_dwCapabilityFlags, IMAP_CAPABILITY_IMAP4rev1)) {
  8152. // No translations! Just copy mailbox name VERBATIM
  8153. if (CP_UNICODE == m_uiDefaultCP) {
  8154. hrResult = ASCIIToUnicode((LPWSTR *)ppszDestination, pszSource, lstrlenA(pszSource));
  8155. if (FAILED(hrResult))
  8156. goto exit;
  8157. }
  8158. else {
  8159. *ppszDestination = PszDupA(pszSource);
  8160. if (NULL == *ppszDestination) {
  8161. hrResult = E_OUTOFMEMORY;
  8162. goto exit;
  8163. }
  8164. }
  8165. // If we reached this point, we succeeded. Return IXP_S_IMAP_VERBATIM_MBOX for
  8166. // verbatim-capable clients so client can mark mailbox with appropriate attributes
  8167. Assert(S_OK == hrResult); // If not S_OK, old IIMAPTransport clients better be able to deal with it
  8168. if (ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_VERBATIMOK))
  8169. hrResult = IXP_S_IMAP_VERBATIM_MBOX;
  8170. goto exit;
  8171. }
  8172. hrResult = ModifiedUTF7ToMultiByte(pszSource, ppszDestination, m_uiDefaultCP, 0);
  8173. exit:
  8174. return hrResult;
  8175. } // _ModifiedUTF7ToMultiByte
  8176. //***************************************************************************
  8177. // Function: ConvertString
  8178. //
  8179. // Purpose:
  8180. // This function allocates a buffer and converts the source string to
  8181. // the target codepage, returning the output buffer. This function also
  8182. // checks to see if the conversion is round-trippable. If not, then a failure
  8183. // result is returned.
  8184. //
  8185. // Arguments:
  8186. // UINT uiSourceCP [in] - codepage of pszSource.
  8187. // UINT uiDestCP [in] - desired codepage of *ppszDest.
  8188. // LPCSTR pszSource [in] - source string to convert to target codepage.
  8189. // int *piSrcLen [in] - caller passes in length of pszSource.
  8190. // LPSTR *ppszDest [out] - if successful, this function returns a pointer
  8191. // to an output buffer containing pszSource translated to uiDestCP.
  8192. // It is the caller's responsibility to MemFree this buffer.
  8193. // int *piDestLen [in/out] - caller passes in maximum expected size of
  8194. // *ppszDest. If caller passes in 0, this function determines the proper
  8195. // size buffer to allocate. If successful, this function returns the
  8196. // length of the output string (which is not necessarily the size of
  8197. // the output buffer).
  8198. // int iOutputExtra [in] - number of extra bytes to allocate in the output
  8199. // buffer. This is useful if the caller wants to append something to
  8200. // the output string.
  8201. //
  8202. // Returns:
  8203. // HRESULT indicating success or failure. Success means that the conversion
  8204. // was roundtrippable, meaning that if you call this function again with
  8205. // *ppszDest as the source, the output will be identical to previous pszSource.
  8206. //***************************************************************************
  8207. HRESULT CImap4Agent::ConvertString(UINT uiSourceCP, UINT uiDestCP,
  8208. LPCSTR pszSource, int *piSrcLen,
  8209. LPSTR *ppszDest, int *piDestLen,
  8210. int iOutputExtra)
  8211. {
  8212. HRESULT hrResult;
  8213. BOOL fResult;
  8214. int iOutputLen;
  8215. LPSTR pszOutput = NULL;
  8216. Assert(NULL != pszSource);
  8217. Assert(NULL != piSrcLen);
  8218. Assert(NULL != ppszDest);
  8219. Assert(NULL != piDestLen);
  8220. // Initialize return values
  8221. *ppszDest = NULL;
  8222. *piDestLen = 0;
  8223. hrResult = m_pInternational->MLANG_ConvertInetReset();
  8224. if (FAILED(hrResult))
  8225. goto exit;
  8226. // Find out how big an output buffer is required, if user doesn't supply a size
  8227. if (*piDestLen == 0) {
  8228. hrResult = m_pInternational->MLANG_ConvertInetString(uiSourceCP, uiDestCP,
  8229. pszSource, piSrcLen, NULL, &iOutputLen);
  8230. if (S_OK != hrResult)
  8231. goto exit;
  8232. }
  8233. else
  8234. iOutputLen = *piDestLen;
  8235. // Allocate the output buffer. Leave room for wide null-term, too
  8236. fResult = MemAlloc((void **)&pszOutput, iOutputLen + iOutputExtra + 2);
  8237. if (FALSE == fResult) {
  8238. hrResult = E_OUTOFMEMORY;
  8239. goto exit;
  8240. }
  8241. // Now perform the conversion
  8242. hrResult = m_pInternational->MLANG_ConvertInetString(uiSourceCP, uiDestCP,
  8243. pszSource, piSrcLen, pszOutput, &iOutputLen);
  8244. if (S_OK != hrResult)
  8245. goto exit;
  8246. // ========================================================*** TAKE OUT after MLANG gets better ***
  8247. // Try the round-trip conversion
  8248. LPSTR pszRoundtrip;
  8249. fResult = MemAlloc((void **)&pszRoundtrip, *piSrcLen + 2);
  8250. if (FALSE == fResult) {
  8251. hrResult = E_OUTOFMEMORY;
  8252. goto exit;
  8253. }
  8254. hrResult = m_pInternational->MLANG_ConvertInetReset();
  8255. if (FAILED(hrResult))
  8256. goto exit;
  8257. int iRoundtripSrc;
  8258. int iRoundtripDest;
  8259. iRoundtripSrc = iOutputLen;
  8260. iRoundtripDest = *piSrcLen;
  8261. hrResult = m_pInternational->MLANG_ConvertInetString(uiDestCP, uiSourceCP,
  8262. pszOutput, &iRoundtripSrc, pszRoundtrip, &iRoundtripDest);
  8263. if (FAILED(hrResult))
  8264. goto exit;
  8265. if (iRoundtripDest != *piSrcLen) {
  8266. MemFree(pszRoundtrip);
  8267. hrResult = S_FALSE;
  8268. goto exit;
  8269. }
  8270. int iRoundtripResult;
  8271. Assert(iRoundtripDest == *piSrcLen);
  8272. if (CP_UNICODE != uiSourceCP)
  8273. iRoundtripResult = StrCmpNA(pszRoundtrip, pszSource, iRoundtripDest);
  8274. else
  8275. iRoundtripResult = StrCmpNW((LPWSTR)pszRoundtrip, (LPCWSTR)pszSource, iRoundtripDest);
  8276. MemFree(pszRoundtrip);
  8277. if (0 != iRoundtripResult)
  8278. hrResult = S_FALSE;
  8279. else
  8280. Assert(S_OK == hrResult);
  8281. // ========================================================*** TAKE OUT after MLANG gets better ***
  8282. exit:
  8283. if (S_OK == hrResult) {
  8284. *ppszDest = pszOutput;
  8285. *piDestLen = iOutputLen;
  8286. }
  8287. else {
  8288. if (SUCCEEDED(hrResult))
  8289. // One or more chars not convertable. We're not round-trippable so we must fail
  8290. hrResult = E_FAIL;
  8291. if (NULL != pszOutput)
  8292. MemFree(pszOutput);
  8293. }
  8294. return hrResult;
  8295. } // ConvertString
  8296. //***************************************************************************
  8297. // Function: HandleFailedTranslation
  8298. //
  8299. // Purpose:
  8300. // In case we cannot translate a mailbox name from modified UTF-7 to
  8301. // the desired codepage (we may not have the codepage, for instance), we
  8302. // provide a duplicate of the modified UTF-7 mailbox name. This function
  8303. // allows the caller to ignore whether target codepage is Unicode or not.
  8304. //
  8305. // Arguments:
  8306. // BOOL fUnicode [in] - If fToUTF7 is TRUE, then this argument indicates
  8307. // whether pszSource points to a Unicode string or not. If fToUTF7 is
  8308. // FALSE, this arg indicates whether *ppszDest should be in Unicode or not.
  8309. // BOOL fToUTF7 [in] - TRUE if we are converting to UTF7, FALSE if we are
  8310. // converting from UTF7.
  8311. // LPCSTR pszSource [in] - pointer to source string.
  8312. // LPSTR *ppszDest [in] - if sucessful, this function returns a pointer
  8313. // to an output buffer containing a duplicate of pszSource (converted
  8314. // to/from Unicode where necessary). It is the caller's responsibility
  8315. // to MemFree this buffer.
  8316. //
  8317. // Returns:
  8318. // HRESULT indicating success or failure.
  8319. //***************************************************************************
  8320. HRESULT CImap4Agent::HandleFailedTranslation(BOOL fUnicode, BOOL fToUTF7,
  8321. LPCSTR pszSource, LPSTR *ppszDest)
  8322. {
  8323. int i;
  8324. int iOutputStep;
  8325. int iInputStep;
  8326. int iSourceLen;
  8327. int iOutputBufSize;
  8328. BOOL fResult;
  8329. LPSTR pszOutput = NULL;
  8330. HRESULT hrResult = S_OK;
  8331. LPCSTR pszIn;
  8332. LPSTR pszOut;
  8333. Assert(m_lRefCount > 0);
  8334. Assert(ISFLAGSET(m_dwTranslateMboxFlags, IMAP_MBOXXLATE_VERBATIMOK));
  8335. // Calculate length of source, size of output buffer
  8336. if (fToUTF7) {
  8337. // Going to UTF7, so output is USASCII
  8338. if (fUnicode) {
  8339. iInputStep = sizeof(WCHAR);
  8340. iSourceLen = lstrlenW((LPCWSTR)pszSource);
  8341. }
  8342. else {
  8343. iInputStep = sizeof(char);
  8344. iSourceLen = lstrlenA(pszSource);
  8345. }
  8346. iOutputStep = sizeof(char);
  8347. iOutputBufSize = iSourceLen + sizeof(char); // Room for null-term
  8348. }
  8349. else {
  8350. // Coming from UTF7, so input is USASCII
  8351. iSourceLen = lstrlenA(pszSource);
  8352. iInputStep = sizeof(char);
  8353. if (fUnicode) {
  8354. iOutputStep = sizeof(WCHAR);
  8355. iOutputBufSize = (iSourceLen + 1) * sizeof(WCHAR); // Room for wide null-term
  8356. }
  8357. else {
  8358. iOutputStep = sizeof(char);
  8359. iOutputBufSize = iSourceLen + sizeof(char); // Room for null-term
  8360. }
  8361. }
  8362. // Allocate output buffer
  8363. fResult = MemAlloc((void **)&pszOutput, iOutputBufSize);
  8364. if (FALSE == fResult) {
  8365. hrResult = E_OUTOFMEMORY;
  8366. goto exit;
  8367. }
  8368. // Copy input to output
  8369. pszIn = pszSource;
  8370. pszOut = pszOutput;
  8371. for (i = 0; i < iSourceLen; i++) {
  8372. char c;
  8373. // Convert input character to USASCII
  8374. if (FALSE == fUnicode || FALSE == fToUTF7)
  8375. c = *pszIn; // Input is already USASCII
  8376. else
  8377. c = *((LPWSTR)pszIn) & 0x00FF; // Convert Unicode to USASCII (too bad if it isn't)
  8378. // Write character to output
  8379. SetUSASCIIChar(FALSE == fToUTF7 && fUnicode, pszOut, c);
  8380. // Advance pointers
  8381. pszIn += iInputStep;
  8382. pszOut += iOutputStep;
  8383. }
  8384. // Null-terminate the output
  8385. SetUSASCIIChar(FALSE == fToUTF7 && fUnicode, pszOut, '\0');
  8386. exit:
  8387. if (SUCCEEDED(hrResult))
  8388. *ppszDest = pszOutput;
  8389. else if (NULL != pszOutput)
  8390. MemFree(pszOutput);
  8391. return hrResult;
  8392. } // HandleFailedTranslation
  8393. //***************************************************************************
  8394. // Function: OnIMAPResponse
  8395. //
  8396. // Purpose:
  8397. // This function dispatches a IIMAPCallback::OnResponse call. The reason
  8398. // to use this function instead of calling directly is watchdog timers: the
  8399. // watchdog timers should be disabled before the call in case the callback
  8400. // puts up some UI, and the watchdog timers should be restarted if they're
  8401. // needed after the callback function returns.
  8402. //
  8403. // Arguments:
  8404. // IIMAPCallback *pCBHandler [in] - a pointer to the IIMAPCallback
  8405. // interface whose OnResponse we should call.
  8406. // IMAP_RESPONSE *pirIMAPResponse [in] - a pointer to the IMAP_RESPONSE
  8407. // structure to send with the IIMAPCallback::OnResponse call.
  8408. //***************************************************************************
  8409. void CImap4Agent::OnIMAPResponse(IIMAPCallback *pCBHandler,
  8410. IMAP_RESPONSE *pirIMAPResponse)
  8411. {
  8412. Assert(NULL != pirIMAPResponse);
  8413. if (NULL == pCBHandler)
  8414. return; // We can't do a damned thing (this can happen due to HandsOffCallback)
  8415. // Suspend watchdog for the duration of this callback
  8416. LeaveBusy();
  8417. pCBHandler->OnResponse(pirIMAPResponse);
  8418. // Re-awaken the watchdog only if we need him
  8419. if (FALSE == m_fBusy &&
  8420. (NULL != m_piciPendingList || (NULL != m_piciCmdInSending &&
  8421. icIDLE_COMMAND != m_piciCmdInSending->icCommandID))) {
  8422. HRESULT hrResult;
  8423. hrResult = HrEnterBusy();
  8424. Assert(SUCCEEDED(hrResult));
  8425. }
  8426. } // OnIMAPResponse
  8427. //***************************************************************************
  8428. // Function: FreeFetchResponse
  8429. //
  8430. // Purpose:
  8431. // This function frees all the allocated data found in a
  8432. // FETCH_CMD_RESULTS_EX structure.
  8433. //
  8434. // Arguments:
  8435. // FETCH_CMD_RESULTS_EX *pcreFreeMe [in] - pointer to the structure to
  8436. // free.
  8437. //***************************************************************************
  8438. void CImap4Agent::FreeFetchResponse(FETCH_CMD_RESULTS_EX *pcreFreeMe)
  8439. {
  8440. SafeMemFree(pcreFreeMe->pszENVSubject);
  8441. FreeIMAPAddresses(pcreFreeMe->piaENVFrom);
  8442. FreeIMAPAddresses(pcreFreeMe->piaENVSender);
  8443. FreeIMAPAddresses(pcreFreeMe->piaENVReplyTo);
  8444. FreeIMAPAddresses(pcreFreeMe->piaENVTo);
  8445. FreeIMAPAddresses(pcreFreeMe->piaENVCc);
  8446. FreeIMAPAddresses(pcreFreeMe->piaENVBcc);
  8447. SafeMemFree(pcreFreeMe->pszENVInReplyTo);
  8448. SafeMemFree(pcreFreeMe->pszENVMessageID);
  8449. } // FreeFetchResponse
  8450. //***************************************************************************
  8451. // Function: FreeIMAPAddresses
  8452. //
  8453. // Purpose:
  8454. // This function frees all the allocated data found in a chain of IMAPADDR
  8455. // structures.
  8456. //
  8457. // Arguments:
  8458. // IMAPADDR *piaFreeMe [in] - pointer to the chain of IMAP addresses to free.
  8459. //***************************************************************************
  8460. void CImap4Agent::FreeIMAPAddresses(IMAPADDR *piaFreeMe)
  8461. {
  8462. while (NULL != piaFreeMe)
  8463. {
  8464. IMAPADDR *piaFreeMeToo;
  8465. SafeMemFree(piaFreeMe->pszName);
  8466. SafeMemFree(piaFreeMe->pszADL);
  8467. SafeMemFree(piaFreeMe->pszMailbox);
  8468. SafeMemFree(piaFreeMe->pszHost);
  8469. // Advance pointer, free structure
  8470. piaFreeMeToo = piaFreeMe;
  8471. piaFreeMe = piaFreeMe->pNext;
  8472. MemFree(piaFreeMeToo);
  8473. }
  8474. } // FreeIMAPAddresses
  8475. //===========================================================================
  8476. // IInternetTransport Abstract Functions
  8477. //===========================================================================
  8478. //***************************************************************************
  8479. // Function: GetServerInfo
  8480. //
  8481. // Purpose:
  8482. // This function copies the module's INETSERVER structure into the given
  8483. // output buffer.
  8484. //
  8485. // Arguments:
  8486. // LPINETSERVER pInetServer [out] - if successful, the function copies
  8487. // the module's INETSERVER structure here.
  8488. //
  8489. // Returns:
  8490. // HRESULT indicating success or failure.
  8491. //***************************************************************************
  8492. HRESULT STDMETHODCALLTYPE CImap4Agent::GetServerInfo(LPINETSERVER pInetServer)
  8493. {
  8494. return CIxpBase::GetServerInfo(pInetServer);
  8495. } // GetServerInfo
  8496. //***************************************************************************
  8497. // Function: GetIXPType
  8498. //
  8499. // Purpose:
  8500. // This function identifies what type of transport this is.
  8501. //
  8502. // Returns:
  8503. // IXP_IMAP for this class.
  8504. //***************************************************************************
  8505. IXPTYPE STDMETHODCALLTYPE CImap4Agent::GetIXPType(void)
  8506. {
  8507. return CIxpBase::GetIXPType();
  8508. } // GetIXPType
  8509. //***************************************************************************
  8510. // Function: IsState
  8511. //
  8512. // Purpose:
  8513. // This function allows a caller to query about the state of the transport
  8514. // interface.
  8515. //
  8516. // Arguments:
  8517. // IXPISSTATE isstate [in] - one of the specified queries defined in
  8518. // imnxport.idl/imnxport.h (eg, IXP_IS_CONNECTED).
  8519. //
  8520. // Returns:
  8521. // HRESULT indicating success or failure. If successful, this function
  8522. // returns S_OK to indicate that the transport is in the specified state,
  8523. // and S_FALSE to indicate that the transport is not in the given state.
  8524. //***************************************************************************
  8525. HRESULT STDMETHODCALLTYPE CImap4Agent::IsState(IXPISSTATE isstate)
  8526. {
  8527. return CIxpBase::IsState(isstate);
  8528. } // IsState
  8529. //***************************************************************************
  8530. // Function: InetServerFromAccount
  8531. //
  8532. // Purpose:
  8533. // This function fills the given INETSERVER structure using the given
  8534. // IImnAccount interface.
  8535. //
  8536. // Arguments:
  8537. // IImnAccount *pAccount [in] - pointer to an IImnAccount interface which
  8538. // the user would like to retrieve information for.
  8539. // LPINETSERVER pInetServer [out] - if successful, the function fills the
  8540. // given INETSERVER structure with information from pAccount.
  8541. //
  8542. // Returns:
  8543. // HRESULT indicating success or failure.
  8544. //***************************************************************************
  8545. HRESULT STDMETHODCALLTYPE CImap4Agent::InetServerFromAccount(IImnAccount *pAccount,
  8546. LPINETSERVER pInetServer)
  8547. {
  8548. return CIxpBase::InetServerFromAccount(pAccount, pInetServer);
  8549. } // InetServerFromAccount
  8550. //***************************************************************************
  8551. // Function: GetStatus
  8552. //
  8553. // Purpose:
  8554. // This function returns the current status of the transport.
  8555. //
  8556. // Arguments:
  8557. // IXPSTATUS *pCurrentStatus [out] - returns current status of transport.
  8558. //
  8559. // Returns:
  8560. // HRESULT indicating success or failure.
  8561. //***************************************************************************
  8562. HRESULT STDMETHODCALLTYPE CImap4Agent::GetStatus(IXPSTATUS *pCurrentStatus)
  8563. {
  8564. return CIxpBase::GetStatus(pCurrentStatus);
  8565. } // GetStatus
  8566. //***************************************************************************
  8567. // Function: SetDefaultCP
  8568. //
  8569. // Purpose:
  8570. // This function allows the caller to tell IIMAPTransport what codepage to
  8571. // use for IMAP mailbox names. After calling this function, all mailbox names
  8572. // submitted to IIMAPTransport will be translated from the default codepage,
  8573. // and all mailbox names returned from the server will be translated to
  8574. // the default codepage before being returned via IIMAPCallback.
  8575. //
  8576. // Arguments:
  8577. // DWORD dwTranslateFlags [in] - enables/disables automatic translation to
  8578. // and from default codepage and IMAP-modified UTF-7. If disabled, caller
  8579. // wishes all mailbox names to be passed verbatim to/from IMAP server.
  8580. // Note that by default we translate for IMAP4 servers, even with its
  8581. // round-trippability problems, because this is how we used to do it
  8582. // in the past.
  8583. // UINT uiCodePage [in] - the default codepage to use for translations.
  8584. // By default this value is the CP returned by GetACP().
  8585. //
  8586. // Returns:
  8587. // HRESULT indicating success or failure.
  8588. //***************************************************************************
  8589. HRESULT STDMETHODCALLTYPE CImap4Agent::SetDefaultCP(DWORD dwTranslateFlags,
  8590. UINT uiCodePage)
  8591. {
  8592. Assert(m_lRefCount > 0);
  8593. if (ISFLAGCLEAR(dwTranslateFlags, IMAP_MBOXXLATE_RETAINCP))
  8594. m_uiDefaultCP = uiCodePage;
  8595. dwTranslateFlags &= ~(IMAP_MBOXXLATE_RETAINCP);
  8596. m_dwTranslateMboxFlags = dwTranslateFlags;
  8597. return S_OK;
  8598. } // SetDefaultCP
  8599. //***************************************************************************
  8600. // Function: SetIdleMode
  8601. //
  8602. // Purpose:
  8603. // The IMAP IDLE extension allows the server to unilaterally report changes
  8604. // to the currently selected mailbox: new email, flag updates, and message
  8605. // expunges. IIMAPTransport always enters IDLE mode when no IMAP commands
  8606. // are pending, but it turns out that this can result in unnecessary
  8607. // entry and exit of IDLE mode when the caller tries to sequence IMAP commands.
  8608. // This function allows the caller to disable the use of the IDLE extension.
  8609. //
  8610. // Arguments:
  8611. // DWORD dwIdleFlags [in] - enables or disables the use of the IDLE extension.
  8612. //
  8613. // Returns:
  8614. // HRESULT indicating success or failure.
  8615. //***************************************************************************
  8616. HRESULT STDMETHODCALLTYPE CImap4Agent::SetIdleMode(DWORD dwIdleFlags)
  8617. {
  8618. Assert(m_lRefCount > 0);
  8619. return E_NOTIMPL;
  8620. } // SetIdleMode
  8621. //***************************************************************************
  8622. // Function: EnableFetchEx
  8623. //
  8624. // Purpose:
  8625. // IIMAPTransport only understood a subset of FETCH response tags. Notable
  8626. // omissions included ENVELOPE and BODYSTRUCTURE. Calling this function
  8627. // changes the behaviour of IIMAPTransport::Fetch. Instead of returning
  8628. // FETCH responses via IIMAPCallback::OnResponse(irtUPDATE_MESSAGE),
  8629. // the FETCH response is returned via OnResponse(irtUPDATE_MESSAGE_EX).
  8630. // Other FETCH-related responses remain unaffected (eg, irtFETCH_BODY).
  8631. //
  8632. // Arguments:
  8633. // DWORD dwFetchExFlags [in] - enables or disables FETCH extensions.
  8634. //
  8635. // Returns:
  8636. // HRESULT indicating success or failure.
  8637. //***************************************************************************
  8638. HRESULT STDMETHODCALLTYPE CImap4Agent::EnableFetchEx(DWORD dwFetchExFlags)
  8639. {
  8640. Assert(m_lRefCount > 0);
  8641. m_dwFetchFlags = dwFetchExFlags;
  8642. return S_OK;
  8643. } // EnableFetchEx
  8644. //===========================================================================
  8645. // CIxpBase Abstract Functions
  8646. //===========================================================================
  8647. //***************************************************************************
  8648. // Function: OnDisconnected
  8649. //
  8650. // Purpose:
  8651. // This function calls FreeAllData to deallocate the structures which are
  8652. // no longer needed when we are disconnected. It then calls
  8653. // CIxpBase::OnDisconnected which updates the user's status.
  8654. //***************************************************************************
  8655. void CImap4Agent::OnDisconnected(void)
  8656. {
  8657. FreeAllData(IXP_E_CONNECTION_DROPPED);
  8658. CIxpBase::OnDisconnected();
  8659. } // OnDisconnected
  8660. //***************************************************************************
  8661. // Function: ResetBase
  8662. //
  8663. // Purpose:
  8664. // This function resets the class to a non-connected state by deallocating
  8665. // the send and receive queues, and the MsgSeqNumToUID table.
  8666. //***************************************************************************
  8667. void CImap4Agent::ResetBase(void)
  8668. {
  8669. FreeAllData(IXP_E_NOT_CONNECTED);
  8670. } // ResetBase
  8671. //***************************************************************************
  8672. // Function: DoQuit
  8673. //
  8674. // Purpose:
  8675. // This function sends a "LOGOUT" command to the IMAP server.
  8676. //***************************************************************************
  8677. void CImap4Agent::DoQuit(void)
  8678. {
  8679. HRESULT hrResult;
  8680. hrResult = NoArgCommand("LOGOUT", icLOGOUT_COMMAND, ssNonAuthenticated, 0, 0,
  8681. DEFAULT_CBHANDLER);
  8682. Assert(SUCCEEDED(hrResult));
  8683. } // DoQuit
  8684. //***************************************************************************
  8685. // Function: OnEnterBusy
  8686. //
  8687. // Purpose:
  8688. // This function does nothing at the current time.
  8689. //***************************************************************************
  8690. void CImap4Agent::OnEnterBusy(void)
  8691. {
  8692. // Do nothing
  8693. } // OnEnterBusy
  8694. //***************************************************************************
  8695. // Function: OnLeaveBusy
  8696. //
  8697. // Purpose:
  8698. // This function does nothing at the current time.
  8699. //***************************************************************************
  8700. void CImap4Agent::OnLeaveBusy(void)
  8701. {
  8702. // Do nothing
  8703. } // OnLeaveBusy