1/* 2 * Copyright 2010-2016, 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 "Protocol.h" 10 11#include "Commands.h" 12 13 14#define DEBUG_IMAP_PROTOCOL 15#ifdef DEBUG_IMAP_PROTOCOL 16# include <stdio.h> 17# define TRACE(...) printf(__VA_ARGS__) 18#else 19# define TRACE(...) ; 20#endif 21 22 23namespace IMAP { 24 25 26Protocol::Protocol() 27 : 28 fSocket(NULL), 29 fBufferedSocket(NULL), 30 fHandlerList(5, false), 31 fCommandID(0), 32 fIsConnected(false) 33{ 34} 35 36 37Protocol::~Protocol() 38{ 39 delete fSocket; 40 delete fBufferedSocket; 41} 42 43 44status_t 45Protocol::Connect(const BNetworkAddress& address, const char* username, 46 const char* password, bool useSSL) 47{ 48 TRACE("Connect\n"); 49 if (useSSL) 50 fSocket = new(std::nothrow) BSecureSocket(address); 51 else 52 fSocket = new(std::nothrow) BSocket(address); 53 54 if (fSocket == NULL) 55 return B_NO_MEMORY; 56 57 status_t status = fSocket->InitCheck(); 58 if (status != B_OK) 59 return status; 60 61 fBufferedSocket = new(std::nothrow) BBufferedDataIO(*fSocket, 32768, false, 62 true); 63 if (fBufferedSocket == NULL) 64 return B_NO_MEMORY; 65 66 TRACE("Login\n"); 67 68 fIsConnected = true; 69 70 LoginCommand login(username, password); 71 status = ProcessCommand(login); 72 if (status != B_OK) { 73 _Disconnect(); 74 return status; 75 } 76 77 _ParseCapabilities(login.Capabilities()); 78 79 if (fCapabilities.IsEmpty()) { 80 CapabilityHandler capabilityHandler; 81 status = ProcessCommand(capabilityHandler); 82 if (status != B_OK) 83 return status; 84 85 _ParseCapabilities(capabilityHandler.Capabilities()); 86 } 87 88 if (Capabilities().Contains("ID")) { 89 // Get the server's ID into our log 90 class IDCommand : public IMAP::Command, public IMAP::Handler { 91 public: 92 BString CommandString() 93 { 94 return "ID NIL"; 95 } 96 97 bool HandleUntagged(IMAP::Response& response) 98 { 99 if (response.IsCommand("ID") && response.IsListAt(1)) { 100 puts("Server:"); 101 ArgumentList& list = response.ListAt(1); 102 for (int32 i = 0; i < list.CountItems(); i += 2) { 103 printf(" %s: %s\n", 104 list.ItemAt(i)->ToString().String(), 105 list.ItemAt(i + 1)->ToString().String()); 106 } 107 return true; 108 } 109 110 return false; 111 } 112 }; 113 IDCommand idCommand; 114 ProcessCommand(idCommand); 115 } 116 return B_OK; 117} 118 119 120status_t 121Protocol::Disconnect() 122{ 123 if (IsConnected()) { 124 RawCommand command("LOGOUT"); 125 ProcessCommand(command); 126 } 127 return _Disconnect(); 128} 129 130 131bool 132Protocol::IsConnected() 133{ 134 return fIsConnected; 135} 136 137 138bool 139Protocol::AddHandler(Handler& handler) 140{ 141 return fHandlerList.AddItem(&handler); 142} 143 144 145void 146Protocol::RemoveHandler(Handler& handler) 147{ 148 fHandlerList.RemoveItem(&handler); 149} 150 151 152status_t 153Protocol::GetFolders(FolderList& folders, BString& separator) 154{ 155 BStringList allFolders; 156 status_t status = _GetAllFolders(allFolders); 157 if (status != B_OK) 158 return status; 159 160 BStringList subscribedFolders; 161 status = GetSubscribedFolders(subscribedFolders, separator); 162 if (status != B_OK) 163 return status; 164 165 for (int32 i = 0; i < allFolders.CountStrings(); i++) { 166 FolderEntry entry; 167 entry.folder = allFolders.StringAt(i); 168 for (int32 j = 0; j < subscribedFolders.CountStrings(); j++) { 169 if (entry.folder == subscribedFolders.StringAt(j)) { 170 entry.subscribed = true; 171 break; 172 } 173 } 174 folders.push_back(entry); 175 } 176 177 // you could be subscribed to a folder which not exist currently, add them: 178 for (int32 i = 0; i < subscribedFolders.CountStrings(); i++) { 179 bool isInlist = false; 180 for (int32 j = 0; j < allFolders.CountStrings(); j++) { 181 if (subscribedFolders.StringAt(i) == allFolders.StringAt(j)) { 182 isInlist = true; 183 break; 184 } 185 } 186 if (isInlist) 187 continue; 188 189 FolderEntry entry; 190 entry.folder = subscribedFolders.StringAt(i); 191 entry.subscribed = true; 192 folders.push_back(entry); 193 } 194 195 return B_OK; 196} 197 198 199status_t 200Protocol::GetSubscribedFolders(BStringList& folders, BString& separator) 201{ 202 ListCommand command(NULL, true); 203 status_t status = ProcessCommand(command); 204 if (status != B_OK) 205 return status; 206 207 folders = command.FolderList(); 208 separator = command.Separator(); 209 return status; 210} 211 212 213status_t 214Protocol::SubscribeFolder(const char* folder) 215{ 216 SubscribeCommand command(folder); 217 return ProcessCommand(command); 218} 219 220 221status_t 222Protocol::UnsubscribeFolder(const char* folder) 223{ 224 UnsubscribeCommand command(folder); 225 return ProcessCommand(command); 226} 227 228 229status_t 230Protocol::GetQuota(uint64& used, uint64& total) 231{ 232 if (!Capabilities().Contains("QUOTA")) 233 return B_ERROR; 234 235 GetQuotaCommand quotaCommand; 236 status_t status = ProcessCommand(quotaCommand); 237 if (status != B_OK) 238 return status; 239 240 used = quotaCommand.UsedStorage(); 241 total = quotaCommand.TotalStorage(); 242 return B_OK; 243} 244 245 246status_t 247Protocol::SendCommand(const char* command) 248{ 249 return SendCommand(0, command); 250} 251 252 253status_t 254Protocol::SendCommand(int32 id, const char* command) 255{ 256 char buffer[2048]; 257 int32 length; 258 if (id > 0) { 259 length = snprintf(buffer, sizeof(buffer), "A%.7" B_PRId32 " %s\r\n", 260 id, command); 261 } else 262 length = snprintf(buffer, sizeof(buffer), "%s\r\n", command); 263 264 TRACE("C: %s", buffer); 265 266 ssize_t bytesWritten = fSocket->Write(buffer, length); 267 if (bytesWritten < 0) 268 return bytesWritten; 269 270 return bytesWritten == length ? B_OK : B_ERROR; 271} 272 273 274ssize_t 275Protocol::SendData(const char* buffer, uint32 length) 276{ 277 return fSocket->Write(buffer, length); 278} 279 280 281status_t 282Protocol::ProcessCommand(Command& command, bigtime_t timeout) 283{ 284 BString commandString = command.CommandString(); 285 if (commandString.IsEmpty()) 286 return B_BAD_VALUE; 287 288 Handler* handler = dynamic_cast<Handler*>(&command); 289 if (handler != NULL && !AddHandler(*handler)) 290 return B_NO_MEMORY; 291 292 int32 commandID = NextCommandID(); 293 status_t status = SendCommand(commandID, commandString.String()); 294 if (status == B_OK) { 295 fOngoingCommands[commandID] = &command; 296 status = HandleResponse(&command, timeout); 297 } 298 299 if (handler != NULL) 300 RemoveHandler(*handler); 301 302 return status; 303} 304 305 306// #pragma mark - protected 307 308 309status_t 310Protocol::HandleResponse(Command* command, bigtime_t timeout, 311 bool disconnectOnTimeout) 312{ 313 status_t commandStatus = B_OK; 314 IMAP::ResponseParser parser(*fBufferedSocket); 315 if (IMAP::LiteralHandler* literalHandler 316 = dynamic_cast<IMAP::LiteralHandler*>(command)) 317 parser.SetLiteralHandler(literalHandler); 318 319 IMAP::Response response; 320 321 bool done = false; 322 while (!done) { 323 try { 324 status_t status = parser.NextResponse(response, timeout); 325 if (status != B_OK) { 326 // we might have lost the connection, clear the connection state 327 if (status != B_TIMED_OUT || disconnectOnTimeout) 328 _Disconnect(); 329 330 return status; 331 } 332 333 if (response.IsUntagged() || response.IsContinuation()) { 334 bool handled = false; 335 for (int32 i = fHandlerList.CountItems(); i-- > 0;) { 336 if (fHandlerList.ItemAt(i)->HandleUntagged(response)) { 337 handled = true; 338 break; 339 } 340 } 341 if (!handled) 342 printf("Unhandled S: %s\n", response.ToString().String()); 343 } else { 344 CommandIDMap::iterator found 345 = fOngoingCommands.find(response.Tag()); 346 if (found != fOngoingCommands.end()) { 347 status_t status = found->second->HandleTagged(response); 348 if (status != B_OK) 349 commandStatus = status; 350 351 fOngoingCommands.erase(found); 352 } else 353 printf("Unknown tag S: %s\n", response.ToString().String()); 354 } 355 } catch (ParseException& exception) { 356 printf("Error during parsing: %s\n", exception.Message()); 357 } catch (StreamException& exception) { 358 return exception.Status(); 359 } 360 361 if (fOngoingCommands.size() == 0) 362 done = true; 363 } 364 365 return commandStatus; 366} 367 368 369int32 370Protocol::NextCommandID() 371{ 372 fCommandID++; 373 return fCommandID; 374} 375 376 377// #pragma mark - private 378 379 380status_t 381Protocol::_Disconnect() 382{ 383 fOngoingCommands.clear(); 384 fIsConnected = false; 385 delete fBufferedSocket; 386 fBufferedSocket = NULL; 387 delete fSocket; 388 fSocket = NULL; 389 390 return B_OK; 391} 392 393 394status_t 395Protocol::_GetAllFolders(BStringList& folders) 396{ 397 ListCommand command(NULL, false); 398 status_t status = ProcessCommand(command); 399 if (status != B_OK) 400 return status; 401 402 folders = command.FolderList(); 403 return status; 404} 405 406 407void 408Protocol::_ParseCapabilities(const ArgumentList& arguments) 409{ 410 fCapabilities.MakeEmpty(); 411 412 for (int32 i = 0; i < arguments.CountItems(); i++) { 413 if (StringArgument* argument 414 = dynamic_cast<StringArgument*>(arguments.ItemAt(i))) 415 fCapabilities.AddItem(new StringArgument(*argument)); 416 } 417 418 TRACE("capabilities: %s\n", fCapabilities.ToString().String()); 419} 420 421 422} // namespace IMAP 423