1/* 2 * Copyright 2010-2011, Haiku Inc. All Rights Reserved. 3 * Copyright 2010 Clemens Zeidler. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9#include "IMAPProtocol.h" 10 11#include "IMAPHandler.h" 12#include "IMAPParser.h" 13 14 15#define DEBUG_IMAP_PROTOCOL 16#ifdef DEBUG_IMAP_PROTOCOL 17# include <stdio.h> 18# define TRACE(x...) printf(x) 19#else 20# define TRACE(x...) ; 21#endif 22 23 24ConnectionReader::ConnectionReader(ServerConnection* connection) 25 : 26 fServerConnection(connection) 27{ 28} 29 30 31status_t 32ConnectionReader::GetNextLine(BString& line, bigtime_t timeout, 33 int32 maxUnfinishedLine) 34{ 35 line.SetTo((const char*)NULL, 0); 36 37 while (true) { 38 status_t status = _GetNextDataBunch(line, timeout); 39 if (status == B_OK) 40 return status; 41 if (status == B_NAME_NOT_FOUND) { 42 if (maxUnfinishedLine < 0 || line.Length() < maxUnfinishedLine) 43 continue; 44 else 45 return status; 46 } 47 return status; 48 } 49 return B_ERROR; 50} 51 52 53status_t 54ConnectionReader::FinishLine(BString& line) 55{ 56 while (true) { 57 status_t status = _GetNextDataBunch(line, B_INFINITE_TIMEOUT); 58 if (status == B_OK) 59 return status; 60 if (status == B_NAME_NOT_FOUND) 61 continue; 62 return status; 63 } 64 return B_ERROR; 65} 66 67 68status_t 69ConnectionReader::ReadToFile(int32 size, BPositionIO* out) 70{ 71 const int32 kBunchSize = 1024; // 1Kb 72 char buffer[kBunchSize]; 73 74 int32 readSize = size - fStringBuffer.Length(); 75 int32 readed = fStringBuffer.Length(); 76 if (readSize < 0) { 77 readed = size; 78 } 79 out->Write(fStringBuffer.String(), readed); 80 fStringBuffer.Remove(0, readed); 81 82 while (readSize > 0) { 83 int32 bunchSize = readSize < kBunchSize ? readSize : kBunchSize; 84 int nReaded = fServerConnection->Read(buffer, bunchSize); 85 if (nReaded < 0) 86 return B_ERROR; 87 readSize -= nReaded; 88 out->Write(buffer, nReaded); 89 } 90 return B_OK; 91} 92 93 94status_t 95ConnectionReader::_GetNextDataBunch(BString& line, bigtime_t timeout, 96 uint32 maxNewLength) 97{ 98 if (_ExtractTillEndOfLine(line)) 99 return B_OK; 100 101 char buffer[maxNewLength]; 102 103 if (timeout != B_INFINITE_TIMEOUT) { 104 status_t status = fServerConnection->WaitForData(timeout); 105 if (status != B_OK) 106 return status; 107 } 108 109 int nReaded = fServerConnection->Read(buffer, maxNewLength); 110 if (nReaded <= 0) 111 return B_ERROR; 112 113 fStringBuffer.SetTo(buffer, nReaded); 114 if (_ExtractTillEndOfLine(line)) 115 return B_OK; 116 return B_NAME_NOT_FOUND; 117} 118 119 120bool 121ConnectionReader::_ExtractTillEndOfLine(BString& out) 122{ 123 int32 endPos = fStringBuffer.FindFirst('\n'); 124 if (endPos == B_ERROR) { 125 endPos = fStringBuffer.FindFirst(xEOF); 126 if (endPos == B_ERROR) { 127 out += fStringBuffer; 128 fStringBuffer.SetTo((const char*)NULL, 0); 129 return false; 130 } 131 } 132 out.Append(fStringBuffer, endPos + 1); 133 fStringBuffer.Remove(0, endPos + 1); 134 135 return true; 136} 137 138 139// #pragma mark - 140 141 142IMAPProtocol::IMAPProtocol() 143 : 144 fServerConnection(&fOwnServerConnection), 145 fConnectionReader(fServerConnection), 146 fCommandID(0), 147 fStopNow(0), 148 fIsConnected(false) 149{ 150} 151 152 153IMAPProtocol::IMAPProtocol(IMAPProtocol& connection) 154 : 155 fServerConnection(connection.fServerConnection), 156 fConnectionReader(fServerConnection), 157 fCommandID(0), 158 fStopNow(0), 159 fIsConnected(false) 160{ 161} 162 163 164IMAPProtocol::~IMAPProtocol() 165{ 166 for (int32 i = 0; i < fAfterQuackCommands.CountItems(); i++) 167 delete fAfterQuackCommands.ItemAt(i); 168} 169 170 171 172void 173IMAPProtocol::SetStopNow() 174{ 175 atomic_set(&fStopNow, 1); 176} 177 178 179bool 180IMAPProtocol::StopNow() 181{ 182 return (atomic_get(&fStopNow) != 0); 183} 184 185 186status_t 187IMAPProtocol::Connect(const char* server, const char* username, 188 const char* password, bool useSSL, int32 port) 189{ 190 TRACE("Connect\n"); 191 status_t status = B_ERROR; 192 if (useSSL) { 193 if (port >= 0) 194 status = fServerConnection->ConnectSSL(server, port); 195 else 196 status = fServerConnection->ConnectSSL(server); 197 } else { 198 if (port >= 0) 199 status = fServerConnection->ConnectSocket(server, port); 200 else 201 status = fServerConnection->ConnectSocket(server); 202 } 203 if (status != B_OK) 204 return status; 205 206 TRACE("Login\n"); 207 208 fIsConnected = true; 209 210 BString command = "LOGIN "; 211 command << "\"" << username << "\" "; 212 command << "\"" << password << "\""; 213 status = ProcessCommand(command); 214 if (status != B_OK) { 215 _Disconnect(); 216 return status; 217 } 218 219 return B_OK; 220} 221 222 223status_t 224IMAPProtocol::Disconnect() 225{ 226 ProcessCommand("LOGOUT"); 227 return _Disconnect(); 228} 229 230 231bool 232IMAPProtocol::IsConnected() 233{ 234 return fIsConnected; 235} 236 237 238ConnectionReader& 239IMAPProtocol::GetConnectionReader() 240{ 241 return fConnectionReader; 242} 243 244 245status_t 246IMAPProtocol::SendRawCommand(const char* command) 247{ 248 static char cmd[256]; 249 ::sprintf(cmd, "%s"CRLF, command); 250 int32 commandLength = strlen(cmd); 251 252 if (fServerConnection->Write(cmd, commandLength) != commandLength) 253 return B_ERROR; 254 return B_OK; 255} 256 257 258int32 259IMAPProtocol::SendRawData(const char* buffer, uint32 nBytes) 260{ 261 return fServerConnection->Write(buffer, nBytes); 262} 263 264 265status_t 266IMAPProtocol::AddAfterQuakeCommand(IMAPCommand* command) 267{ 268 return fAfterQuackCommands.AddItem(command); 269} 270 271 272status_t 273IMAPProtocol::ProcessCommand(IMAPCommand* command, bigtime_t timeout) 274{ 275 status_t status = _ProcessCommandWithoutAfterQuake(command, timeout); 276 277 ProcessAfterQuacks(timeout); 278 279 return status; 280} 281 282 283status_t 284IMAPProtocol::ProcessCommand(const char* command, bigtime_t timeout) 285{ 286 status_t status = _ProcessCommandWithoutAfterQuake(command, timeout); 287 288 ProcessAfterQuacks(timeout); 289 return status; 290} 291 292 293status_t 294IMAPProtocol::SendCommand(const char* command, int32 commandID) 295{ 296 if (strlen(command) + 10 > 256) 297 return B_NO_MEMORY; 298 299 static char cmd[256]; 300 ::sprintf(cmd, "A%.7ld %s"CRLF, commandID, command); 301 302 TRACE("_SendCommand: %s\n", cmd); 303 int commandLength = strlen(cmd); 304 if (fServerConnection->Write(cmd, commandLength) != commandLength) { 305 // we might lost the connection, clear the connection state 306 _Disconnect(); 307 return B_ERROR; 308 } 309 310 fOngoingCommands.push_back(commandID); 311 return B_OK; 312} 313 314 315status_t 316IMAPProtocol::HandleResponse(int32 commandID, bigtime_t timeout, 317 bool disconnectOnTimeout) 318{ 319 status_t commandStatus = B_ERROR; 320 321 bool done = false; 322 while (done != true) { 323 BString line; 324 status_t status = fConnectionReader.GetNextLine(line, timeout); 325 if (status != B_OK) { 326 // we might lost the connection, clear the connection state 327 if (status != B_TIMED_OUT) { 328 TRACE("S:read error %s", line.String()); 329 _Disconnect(); 330 } else if (disconnectOnTimeout) { 331 _Disconnect(); 332 } 333 return status; 334 } 335 //TRACE("S: %s", line.String()); 336 337 bool handled = false; 338 for (int i = 0; i < fHandlerList.CountItems(); i++) { 339 if (fHandlerList.ItemAt(i)->Handle(line) == true) { 340 handled = true; 341 break; 342 } 343 } 344 if (handled == true) 345 continue; 346 347 for (std::vector<int32>::iterator it = fOngoingCommands.begin(); 348 it != fOngoingCommands.end(); it++) { 349 static char idString[8]; 350 ::sprintf(idString, "A%.7ld", *it); 351 if (line.FindFirst(idString) >= 0) { 352 if (*it == commandID) { 353 BString result = IMAPParser::ExtractElementAfter(line, 354 idString); 355 if (result == "OK") 356 commandStatus = B_OK; 357 else { 358 fCommandError = IMAPParser::ExtractStringAfter(line, 359 idString); 360 TRACE("Command Error %s\n", fCommandError.String()); 361 } 362 } 363 fOngoingCommands.erase(it); 364 break; 365 } 366 } 367 if (fOngoingCommands.size() == 0) 368 done = true; 369 370 TRACE("Unhandled S: %s", line.String()); 371 } 372 373 return commandStatus; 374} 375 376 377void 378IMAPProtocol::ProcessAfterQuacks(bigtime_t timeout) 379{ 380 while (fAfterQuackCommands.CountItems() != 0) { 381 IMAPCommand* currentCommand = fAfterQuackCommands.RemoveItemAt(0); 382 _ProcessCommandWithoutAfterQuake(currentCommand, timeout); 383 delete currentCommand; 384 } 385} 386 387 388int32 389IMAPProtocol::NextCommandID() 390{ 391 fCommandID++; 392 return fCommandID; 393} 394 395 396status_t 397IMAPProtocol::_ProcessCommandWithoutAfterQuake(IMAPCommand* command, 398 bigtime_t timeout) 399{ 400 BString cmd = command->Command(); 401 if (cmd == "") 402 return B_BAD_VALUE; 403 if (!fHandlerList.AddItem(command, 0)) 404 return B_NO_MEMORY; 405 406 status_t status = _ProcessCommandWithoutAfterQuake(cmd, timeout); 407 408 fHandlerList.RemoveItem(command); 409 410 return status; 411} 412 413 414status_t 415IMAPProtocol::_ProcessCommandWithoutAfterQuake(const char* command, 416 bigtime_t timeout) 417{ 418 int32 commandID = NextCommandID(); 419 status_t status = SendCommand(command, commandID); 420 if (status != B_OK) 421 return status; 422 423 return HandleResponse(commandID, timeout); 424} 425 426 427status_t 428IMAPProtocol::_Disconnect() 429{ 430 fOngoingCommands.clear(); 431 fIsConnected = false; 432 return fOwnServerConnection.Disconnect(); 433} 434