1 /* 2 * Copyright 2010-2015, 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 23 namespace IMAP { 24 25 26 Protocol::Protocol() 27 : 28 fSocket(NULL), 29 fBufferedSocket(NULL), 30 fHandlerList(5, false), 31 fCommandID(0), 32 fIsConnected(false) 33 { 34 } 35 36 37 Protocol::~Protocol() 38 { 39 delete fSocket; 40 delete fBufferedSocket; 41 } 42 43 44 status_t 45 Protocol::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 120 status_t 121 Protocol::Disconnect() 122 { 123 if (IsConnected()) { 124 RawCommand command("LOGOUT"); 125 ProcessCommand(command); 126 } 127 return _Disconnect(); 128 } 129 130 131 bool 132 Protocol::IsConnected() 133 { 134 return fIsConnected; 135 } 136 137 138 bool 139 Protocol::AddHandler(Handler& handler) 140 { 141 return fHandlerList.AddItem(&handler); 142 } 143 144 145 void 146 Protocol::RemoveHandler(Handler& handler) 147 { 148 fHandlerList.RemoveItem(&handler); 149 } 150 151 152 status_t 153 Protocol::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.folder.ICompare("INBOX") == 0) { 171 entry.subscribed = true; 172 break; 173 } 174 } 175 folders.push_back(entry); 176 } 177 178 // you could be subscribed to a folder which not exist currently, add them: 179 for (int32 i = 0; i < subscribedFolders.CountStrings(); i++) { 180 bool isInlist = false; 181 for (int32 j = 0; j < allFolders.CountStrings(); j++) { 182 if (subscribedFolders.StringAt(i) == allFolders.StringAt(j)) { 183 isInlist = true; 184 break; 185 } 186 } 187 if (isInlist) 188 continue; 189 190 FolderEntry entry; 191 entry.folder = subscribedFolders.StringAt(i); 192 entry.subscribed = true; 193 folders.push_back(entry); 194 } 195 196 return B_OK; 197 } 198 199 200 status_t 201 Protocol::GetSubscribedFolders(BStringList& folders, BString& separator) 202 { 203 ListCommand command(NULL, true); 204 status_t status = ProcessCommand(command); 205 if (status != B_OK) 206 return status; 207 208 folders = command.FolderList(); 209 separator = command.Separator(); 210 return status; 211 } 212 213 214 status_t 215 Protocol::SubscribeFolder(const char* folder) 216 { 217 SubscribeCommand command(folder); 218 return ProcessCommand(command); 219 } 220 221 222 status_t 223 Protocol::UnsubscribeFolder(const char* folder) 224 { 225 UnsubscribeCommand command(folder); 226 return ProcessCommand(command); 227 } 228 229 230 status_t 231 Protocol::GetQuota(uint64& used, uint64& total) 232 { 233 if (!Capabilities().Contains("QUOTA")) 234 return B_ERROR; 235 236 GetQuotaCommand quotaCommand; 237 status_t status = ProcessCommand(quotaCommand); 238 if (status != B_OK) 239 return status; 240 241 used = quotaCommand.UsedStorage(); 242 total = quotaCommand.TotalStorage(); 243 return B_OK; 244 } 245 246 247 status_t 248 Protocol::SendCommand(const char* command) 249 { 250 return SendCommand(0, command); 251 } 252 253 254 status_t 255 Protocol::SendCommand(int32 id, const char* command) 256 { 257 char buffer[2048]; 258 int32 length; 259 if (id > 0) { 260 length = snprintf(buffer, sizeof(buffer), "A%.7" B_PRId32 " %s\r\n", 261 id, command); 262 } else 263 length = snprintf(buffer, sizeof(buffer), "%s\r\n", command); 264 265 TRACE("C: %s", buffer); 266 267 ssize_t bytesWritten = fSocket->Write(buffer, length); 268 if (bytesWritten < 0) 269 return bytesWritten; 270 271 return bytesWritten == length ? B_OK : B_ERROR; 272 } 273 274 275 ssize_t 276 Protocol::SendData(const char* buffer, uint32 length) 277 { 278 return fSocket->Write(buffer, length); 279 } 280 281 282 status_t 283 Protocol::ProcessCommand(Command& command, bigtime_t timeout) 284 { 285 BString commandString = command.CommandString(); 286 if (commandString.IsEmpty()) 287 return B_BAD_VALUE; 288 289 Handler* handler = dynamic_cast<Handler*>(&command); 290 if (handler != NULL && !AddHandler(*handler)) 291 return B_NO_MEMORY; 292 293 int32 commandID = NextCommandID(); 294 status_t status = SendCommand(commandID, commandString.String()); 295 if (status == B_OK) { 296 fOngoingCommands[commandID] = &command; 297 status = HandleResponse(&command, timeout); 298 } 299 300 if (handler != NULL) 301 RemoveHandler(*handler); 302 303 return status; 304 } 305 306 307 // #pragma mark - protected 308 309 310 status_t 311 Protocol::HandleResponse(Command* command, bigtime_t timeout, 312 bool disconnectOnTimeout) 313 { 314 status_t commandStatus = B_OK; 315 IMAP::ResponseParser parser(*fBufferedSocket); 316 if (IMAP::LiteralHandler* literalHandler 317 = dynamic_cast<IMAP::LiteralHandler*>(command)) 318 parser.SetLiteralHandler(literalHandler); 319 320 IMAP::Response response; 321 322 bool done = false; 323 while (!done) { 324 try { 325 status_t status = parser.NextResponse(response, timeout); 326 if (status != B_OK) { 327 // we might have lost the connection, clear the connection state 328 if (status != B_TIMED_OUT || disconnectOnTimeout) 329 _Disconnect(); 330 331 return status; 332 } 333 334 if (response.IsUntagged() || response.IsContinuation()) { 335 bool handled = false; 336 for (int32 i = fHandlerList.CountItems(); i-- > 0;) { 337 if (fHandlerList.ItemAt(i)->HandleUntagged(response)) { 338 handled = true; 339 break; 340 } 341 } 342 if (!handled) 343 printf("Unhandled S: %s\n", response.ToString().String()); 344 } else { 345 CommandIDMap::iterator found 346 = fOngoingCommands.find(response.Tag()); 347 if (found != fOngoingCommands.end()) { 348 status_t status = found->second->HandleTagged(response); 349 if (status != B_OK) 350 commandStatus = status; 351 352 fOngoingCommands.erase(found); 353 } else 354 printf("Unknown tag S: %s\n", response.ToString().String()); 355 } 356 } catch (ParseException& exception) { 357 printf("Error during parsing: %s\n", exception.Message()); 358 } 359 360 if (fOngoingCommands.size() == 0) 361 done = true; 362 } 363 364 return commandStatus; 365 } 366 367 368 int32 369 Protocol::NextCommandID() 370 { 371 fCommandID++; 372 return fCommandID; 373 } 374 375 376 // #pragma mark - private 377 378 379 status_t 380 Protocol::_Disconnect() 381 { 382 fOngoingCommands.clear(); 383 fIsConnected = false; 384 delete fBufferedSocket; 385 fBufferedSocket = NULL; 386 delete fSocket; 387 fSocket = NULL; 388 389 return B_OK; 390 } 391 392 393 status_t 394 Protocol::_GetAllFolders(BStringList& folders) 395 { 396 ListCommand command(NULL, false); 397 status_t status = ProcessCommand(command); 398 if (status != B_OK) 399 return status; 400 401 folders = command.FolderList(); 402 return status; 403 } 404 405 406 void 407 Protocol::_ParseCapabilities(const ArgumentList& arguments) 408 { 409 fCapabilities.MakeEmpty(); 410 411 for (int32 i = 0; i < arguments.CountItems(); i++) { 412 if (StringArgument* argument 413 = dynamic_cast<StringArgument*>(arguments.ItemAt(i))) 414 fCapabilities.AddItem(new StringArgument(*argument)); 415 } 416 417 TRACE("capabilities: %s\n", fCapabilities.ToString().String()); 418 } 419 420 421 } // namespace IMAP 422