PageRenderTime 60ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/mojomail/imap/src/client/ImapRequestManager.cpp

https://bitbucket.org/kasimling/app-services
C++ | 403 lines | 264 code | 65 blank | 74 comment | 57 complexity | 8eb078c7a25350065de8fe7ffb7467be MD5 | raw file
  1. // @@@LICENSE
  2. //
  3. // Copyright (c) 2010-2012 Hewlett-Packard Development Company, L.P.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. // LICENSE@@@
  18. #include "client/ImapRequestManager.h"
  19. #include "client/ImapSession.h"
  20. #include "protocol/UntaggedUpdateParser.h"
  21. #include <sstream>
  22. #include "ImapPrivate.h"
  23. using namespace std;
  24. MojLogger ImapRequestManager::s_protocolLog("com.palm.mail.protocol");
  25. ImapRequestManager::ImapRequestManager(ImapSession& session)
  26. : m_session(session),
  27. m_log(session.GetLogger()),
  28. m_currentRequestId(0),
  29. m_handlingResponses(false),
  30. m_waitingForLine(false),
  31. m_untaggedUpdateParser(new UntaggedUpdateParser(m_session)),
  32. m_handleResponsesSlot(this, &ImapRequestManager::HandleResponses),
  33. m_handleMoreDataSlot(this, &ImapRequestManager::HandleMoreData)
  34. {
  35. }
  36. ImapRequestManager::~ImapRequestManager()
  37. {
  38. }
  39. std::string ImapRequestManager::SendRequest(const std::string& request, bool canLogRequest)
  40. {
  41. int id = ++m_currentRequestId;
  42. stringstream ss;
  43. // Create tag prefixed by "~A" from the current request id counter
  44. ss << "~A" << id;
  45. std::string tag = ss.str();
  46. // Combine tag and request, e.g. ~A15 CAPABILITY
  47. ss << " " << request << "\r\n";
  48. std::string line = ss.str();
  49. if(canLogRequest)
  50. MojLogDebug(s_protocolLog, "[session %p] sending: %s %s", &m_session, tag.c_str(), request.c_str());
  51. OutputStreamPtr& out = m_session.GetOutputStream();
  52. // note: this could throw an exception
  53. out->Write(line);
  54. out->Flush();
  55. return tag;
  56. }
  57. void ImapRequestManager::SendRequest(const string& request, const MojRefCountedPtr<ImapResponseParser>& responseParser, int timeoutSeconds, bool canLogRequest)
  58. {
  59. assert( !request.empty() );
  60. assert( responseParser.get() );
  61. std::string tag = SendRequest(request, canLogRequest);
  62. m_pendingRequests.push_back( PendingRequest(tag, responseParser, timeoutSeconds) );
  63. // Don't call WaitForResponses if someone is already using the LineReader
  64. if(!Busy()) {
  65. WaitForResponses();
  66. }
  67. }
  68. void ImapRequestManager::UpdateRequestTimeout(const MojRefCountedPtr<ImapResponseParser>& responseParser, int timeoutSeconds)
  69. {
  70. std::vector<PendingRequest>::iterator it;
  71. for(it = m_pendingRequests.begin(); it != m_pendingRequests.end(); ++it) {
  72. if(it->parser == responseParser) {
  73. it->timeout = timeoutSeconds;
  74. }
  75. }
  76. if(m_waitingForLine) {
  77. int newTimeout = GetNextTimeout();
  78. MojLogDebug(m_log, "updating request timeout to %d seconds", newTimeout);
  79. m_session.GetLineReader()->SetTimeout(newTimeout);
  80. } else {
  81. // If we're not waiting for a line, then either it'll start waiting soon, or
  82. // we shouldn't be messing with the line reader right now.
  83. }
  84. }
  85. int ImapRequestManager::GetNextTimeout()
  86. {
  87. int timeout = 0;
  88. // Get the max timeout out of all the requests
  89. BOOST_FOREACH(const PendingRequest& request, m_pendingRequests) {
  90. if(request.timeout == 0) {
  91. timeout = 0;
  92. break;
  93. } else if(request.timeout > timeout) {
  94. timeout = request.timeout;
  95. }
  96. }
  97. return timeout;
  98. }
  99. void ImapRequestManager::WaitForResponses()
  100. {
  101. MojLogTrace(m_log);
  102. m_waitingForLine = true;
  103. try {
  104. int timeout = GetNextTimeout();
  105. if(timeout > 0)
  106. MojLogDebug(m_log, "waiting for responses; timeout in %d seconds", timeout);
  107. else
  108. MojLogDebug(m_log, "waiting for responses; no timeout");
  109. MojRefCountedPtr<LineReader> lineReader = m_session.GetLineReader();
  110. lineReader->WaitForLine(m_handleResponsesSlot, timeout);
  111. } catch(const exception& e) {
  112. MojLogError(m_log, "exception waiting for responses: %s", e.what());
  113. ReportException(e);
  114. } catch(...) {
  115. MojLogError(m_log, "unknown exception waiting for responses");
  116. ReportException(UNKNOWN_EXCEPTION);
  117. }
  118. }
  119. /**
  120. * Handle response lines from server.
  121. * Typically the request/response goes like this: (C is client, S is server)
  122. *
  123. * C: ~A1 UID FETCH 1:* FLAGS
  124. * S: * 1 FETCH (UID 2 FLAGS (\Seen))
  125. * S: * 2 FETCH (UID 7 FLAGS (\Seen))
  126. * S: * 3 FETCH (UID 8 FLAGS (\Seen))
  127. * S: ~A1 OK FETCH completed
  128. *
  129. * The lines starting with '*' are untagged responses.
  130. * The line starting with ~A1 is a tagged response.
  131. *
  132. * In general, we only execute one command at a time, so that the current
  133. * command can handle the untagged responses (otherwise, for commands like
  134. * SEARCH, we don't know which query triggered the untagged responses).
  135. *
  136. * Note that we can also get extra unsolicited untagged responses from the server,
  137. * which don't correspond to the current command. Usually, it's something like
  138. * "* 15 EXISTS" or "* EXPUNGE 3" to indicate that the number of messages in the
  139. * currently selected folder has changed.
  140. *
  141. * These events must be handled in order to accurately maintain the UID to message
  142. * number mapping.
  143. */
  144. MojErr ImapRequestManager::HandleResponses()
  145. {
  146. MojLogTrace(m_log);
  147. m_waitingForLine = false;
  148. m_handlingResponses = true;
  149. try {
  150. MojRefCountedPtr<LineReader> lineReader = m_session.GetLineReader();
  151. lineReader->CheckError();
  152. while(lineReader->MoreLinesInBuffer()) {
  153. string line = lineReader->ReadLine();
  154. MojLogDebug(s_protocolLog, "[session %p] response: %s", &m_session, line.c_str());
  155. HandleResponseLine(line);
  156. // If a parser is active, stop reading from the LineReader
  157. if(m_currentParser.get()) {
  158. break;
  159. }
  160. }
  161. // After we finish reading all lines from the buffer, return
  162. if(!m_pendingRequests.empty() && !m_currentParser.get()) {
  163. WaitForResponses();
  164. }
  165. } catch (const exception& e) {
  166. MojLogError(m_log, "%s: exception handling response: %s", __PRETTY_FUNCTION__, e.what());
  167. m_session.FatalError("exception in ImapRequestManager::HandleResponses");
  168. ReportException(e);
  169. } catch (...) {
  170. MojLogError(m_log, "unknown exception");
  171. ReportException(UNKNOWN_EXCEPTION);
  172. }
  173. m_handlingResponses = false;
  174. return MojErrNone;
  175. }
  176. /**
  177. * Handle one line of response.
  178. *
  179. * @return whether more lines are expected
  180. */
  181. void ImapRequestManager::HandleResponseLine(const string& line)
  182. {
  183. if(line.length() > 0) {
  184. if(line.at(0) == '*' && line.length() > 2) {
  185. // untagged response
  186. bool handled = false;
  187. BOOST_FOREACH(const PendingRequest& request, m_pendingRequests) {
  188. handled = request.parser->HandleUntaggedResponse(line.substr(2));
  189. // Stop as soon as one parser claims to handle the response, or
  190. // if a untagged response handler needs to parse more data.
  191. if(handled || m_currentParser.get()) break;
  192. }
  193. if(!handled && m_currentParser.get() == NULL) {
  194. // TODO should call a generic untagged response handler
  195. HandleGenericUntaggedResponse(line.substr(2));
  196. }
  197. } else if(line.at(0) == '+') {
  198. // TODO: handle continuation by passing it to the first response handler.
  199. // The response handler would probably start sending data to the server.
  200. // For now, just ignore it
  201. bool handled = false;
  202. if(!m_pendingRequests.empty()) {
  203. handled = m_pendingRequests[0].parser->HandleContinuationResponse();
  204. }
  205. if(!handled) {
  206. // This shouldn't ever happen
  207. MojLogWarning(m_log, "unhandled continuation response");
  208. }
  209. } else {
  210. // tagged response
  211. ImapStatusCode status;
  212. string tag, response;
  213. try {
  214. ImapResponseParser::SplitResponse(line, tag, status, response);
  215. } catch(const MailException& e) {
  216. MojLogError(m_log, "error parsing response: %s", line.c_str());
  217. throw;
  218. }
  219. if(boost::starts_with(response, "[ALERT]")) {
  220. MojLogWarning(m_log, "server warning: \"%s\"", response.c_str());
  221. }
  222. // Find a pending request with a matching tag
  223. // Don't call the callback until after this loop, to avoid reentrant SendRequest calls
  224. std::vector<PendingRequest>::iterator it;
  225. MojRefCountedPtr<ImapResponseParser> parser;
  226. for(it = m_pendingRequests.begin(); it != m_pendingRequests.end(); ++it) {
  227. if(tag == it->tag) {
  228. parser = it->parser;
  229. m_pendingRequests.erase(it);
  230. break;
  231. }
  232. }
  233. // Check if we found a matching tag
  234. if(parser.get()) {
  235. parser->BaseHandleResponse(status, response);
  236. } else {
  237. MojLogError(m_log, "no response handler for tag '%s'", tag.c_str());
  238. }
  239. }
  240. }
  241. }
  242. void ImapRequestManager::RequestLine(const MojRefCountedPtr<ImapResponseParser>& responseParser, int timeoutSeconds)
  243. {
  244. assert( !m_waitingForLine );
  245. assert( m_currentParser.get() == NULL || m_currentParser == responseParser );
  246. WaitForParser(responseParser);
  247. m_session.GetLineReader()->WaitForLine(m_handleMoreDataSlot, timeoutSeconds);
  248. }
  249. void ImapRequestManager::RequestData(const MojRefCountedPtr<ImapResponseParser>& responseParser, size_t bytes, int timeoutSeconds)
  250. {
  251. assert( !m_waitingForLine );
  252. assert( m_currentParser.get() == NULL || m_currentParser == responseParser );
  253. WaitForParser(responseParser);
  254. m_session.GetLineReader()->WaitForData(m_handleMoreDataSlot, bytes, timeoutSeconds);
  255. }
  256. // Used to service requests by parsers for additional data
  257. MojErr ImapRequestManager::HandleMoreData()
  258. {
  259. if(m_currentParser.get()) {
  260. bool needsMoreData;
  261. try {
  262. needsMoreData = m_currentParser->HandleAdditionalData();
  263. } catch(const exception& e) {
  264. MojLogError(m_log, "exception in %s: %s", __PRETTY_FUNCTION__, e.what());
  265. // Generally, we can't recover from this kind of parser error
  266. // because we don't know the state of the connection.
  267. // Kill the connection
  268. m_session.FatalError("exception in ImapRequestManager::HandleMoreData");
  269. // Kill all pending requests
  270. ReportException(e);
  271. return MojErrNone;
  272. }
  273. // When we're done, start listening for command responses again
  274. if(!needsMoreData) {
  275. ParserFinished(m_currentParser);
  276. }
  277. } else {
  278. MojLogError(m_log, "%s called without a parser", __func__);
  279. }
  280. return MojErrNone;
  281. }
  282. void ImapRequestManager::WaitForParser(const MojRefCountedPtr<ImapResponseParser>& parser)
  283. {
  284. if(m_currentParser.get() == NULL || m_currentParser == parser) {
  285. m_currentParser = parser;
  286. } else {
  287. throw MailException("already have an active parser", __FILE__, __LINE__);
  288. }
  289. }
  290. void ImapRequestManager::ParserFinished(const MojRefCountedPtr<ImapResponseParser>& parser)
  291. {
  292. if(m_currentParser == parser) {
  293. m_currentParser.reset();
  294. if(!m_pendingRequests.empty() && !m_waitingForLine && !m_handlingResponses) {
  295. WaitForResponses();
  296. }
  297. }
  298. }
  299. void ImapRequestManager::HandleGenericUntaggedResponse(const std::string& line)
  300. {
  301. try {
  302. m_untaggedUpdateParser->HandleUntaggedResponse(line);
  303. } catch(const exception& e) {
  304. MojLogError(m_log, "caught exception in %s: %s", __PRETTY_FUNCTION__, e.what());
  305. ReportException(e);
  306. } catch(...) {
  307. MojLogError(m_log, "unknown exception in %s", __PRETTY_FUNCTION__);
  308. ReportException(UNKNOWN_EXCEPTION);
  309. }
  310. }
  311. void ImapRequestManager::ReportException(const exception& e)
  312. {
  313. BOOST_FOREACH(const PendingRequest& request, m_pendingRequests) {
  314. MojRefCountedPtr<ImapResponseParser> parser = request.parser;
  315. parser->HandleError(e);
  316. }
  317. Reset();
  318. }
  319. void ImapRequestManager::CancelRequests()
  320. {
  321. ReportException( MailException("cancelling requests", __FILE__, __LINE__) );
  322. Reset();
  323. }
  324. void ImapRequestManager::Reset()
  325. {
  326. m_pendingRequests.clear();
  327. m_currentRequestId = 0;
  328. }
  329. void ImapRequestManager::Status(MojObject& status)
  330. {
  331. MojErr err;
  332. err = status.put("waitingForLine", m_waitingForLine);
  333. ErrorToException(err);
  334. err = status.put("numPendingRequests", (MojInt64) m_pendingRequests.size());
  335. ErrorToException(err);
  336. err = status.put("timeout", GetNextTimeout());
  337. ErrorToException(err);
  338. }