* Copyright 2010-2016, Haiku Inc. All Rights Reserved.
* Copyright 2010 Clemens Zeidler. All rights reserved.
*
* Distributed under the terms of the MIT License.
*/
#include "Protocol.h"
#include "Commands.h"
#define DEBUG_IMAP_PROTOCOL
#ifdef DEBUG_IMAP_PROTOCOL
# include <stdio.h>
# define TRACE(...) printf(__VA_ARGS__)
#else
# define TRACE(...) ;
#endif
namespace IMAP {
Protocol::Protocol()
:
fSocket(NULL),
fBufferedSocket(NULL),
fHandlerList(5),
fCommandID(0),
fIsConnected(false)
{
}
Protocol::~Protocol()
{
delete fSocket;
delete fBufferedSocket;
}
status_t
Protocol::Connect(const BNetworkAddress& address, const char* username,
const char* password, bool useSSL)
{
TRACE("Connect\n");
if (useSSL)
fSocket = new(std::nothrow) BSecureSocket(address);
else
fSocket = new(std::nothrow) BSocket(address);
if (fSocket == NULL)
return B_NO_MEMORY;
status_t status = fSocket->InitCheck();
if (status != B_OK)
return status;
fBufferedSocket = new(std::nothrow) BBufferedDataIO(*fSocket, 32768, false,
true);
if (fBufferedSocket == NULL)
return B_NO_MEMORY;
TRACE("Login\n");
fIsConnected = true;
LoginCommand login(username, password);
status = ProcessCommand(login);
if (status != B_OK) {
_Disconnect();
return status;
}
_ParseCapabilities(login.Capabilities());
if (fCapabilities.IsEmpty()) {
CapabilityHandler capabilityHandler;
status = ProcessCommand(capabilityHandler);
if (status != B_OK)
return status;
_ParseCapabilities(capabilityHandler.Capabilities());
}
if (Capabilities().Contains("ID")) {
class IDCommand : public IMAP::Command, public IMAP::Handler {
public:
BString CommandString()
{
return "ID NIL";
}
bool HandleUntagged(IMAP::Response& response)
{
if (response.IsCommand("ID") && response.IsListAt(1)) {
puts("Server:");
ArgumentList& list = response.ListAt(1);
for (int32 i = 0; i < list.CountItems(); i += 2) {
printf(" %s: %s\n",
list.ItemAt(i)->ToString().String(),
list.ItemAt(i + 1)->ToString().String());
}
return true;
}
return false;
}
};
IDCommand idCommand;
ProcessCommand(idCommand);
}
return B_OK;
}
status_t
Protocol::Disconnect()
{
if (IsConnected()) {
RawCommand command("LOGOUT");
ProcessCommand(command);
}
return _Disconnect();
}
bool
Protocol::IsConnected()
{
return fIsConnected;
}
bool
Protocol::AddHandler(Handler& handler)
{
return fHandlerList.AddItem(&handler);
}
void
Protocol::RemoveHandler(Handler& handler)
{
fHandlerList.RemoveItem(&handler);
}
status_t
Protocol::GetFolders(FolderList& folders, BString& separator)
{
BStringList allFolders;
status_t status = _GetAllFolders(allFolders);
if (status != B_OK)
return status;
BStringList subscribedFolders;
status = GetSubscribedFolders(subscribedFolders, separator);
if (status != B_OK)
return status;
for (int32 i = 0; i < allFolders.CountStrings(); i++) {
FolderEntry entry;
entry.folder = allFolders.StringAt(i);
for (int32 j = 0; j < subscribedFolders.CountStrings(); j++) {
if (entry.folder == subscribedFolders.StringAt(j)) {
entry.subscribed = true;
break;
}
}
folders.push_back(entry);
}
for (int32 i = 0; i < subscribedFolders.CountStrings(); i++) {
bool isInlist = false;
for (int32 j = 0; j < allFolders.CountStrings(); j++) {
if (subscribedFolders.StringAt(i) == allFolders.StringAt(j)) {
isInlist = true;
break;
}
}
if (isInlist)
continue;
FolderEntry entry;
entry.folder = subscribedFolders.StringAt(i);
entry.subscribed = true;
folders.push_back(entry);
}
return B_OK;
}
status_t
Protocol::GetSubscribedFolders(BStringList& folders, BString& separator)
{
ListCommand command(NULL, true);
status_t status = ProcessCommand(command);
if (status != B_OK)
return status;
folders = command.FolderList();
separator = command.Separator();
return status;
}
status_t
Protocol::SubscribeFolder(const char* folder)
{
SubscribeCommand command(folder);
return ProcessCommand(command);
}
status_t
Protocol::UnsubscribeFolder(const char* folder)
{
UnsubscribeCommand command(folder);
return ProcessCommand(command);
}
status_t
Protocol::GetQuota(uint64& used, uint64& total)
{
if (!Capabilities().Contains("QUOTA"))
return B_ERROR;
GetQuotaCommand quotaCommand;
status_t status = ProcessCommand(quotaCommand);
if (status != B_OK)
return status;
used = quotaCommand.UsedStorage();
total = quotaCommand.TotalStorage();
return B_OK;
}
status_t
Protocol::SendCommand(const char* command)
{
return SendCommand(0, command);
}
status_t
Protocol::SendCommand(int32 id, const char* command)
{
char buffer[2048];
int32 length;
if (id > 0) {
length = snprintf(buffer, sizeof(buffer), "A%.7" B_PRId32 " %s\r\n",
id, command);
} else
length = snprintf(buffer, sizeof(buffer), "%s\r\n", command);
TRACE("C: %s", buffer);
ssize_t bytesWritten = fSocket->Write(buffer, length);
if (bytesWritten < 0)
return bytesWritten;
return bytesWritten == length ? B_OK : B_ERROR;
}
ssize_t
Protocol::SendData(const char* buffer, uint32 length)
{
return fSocket->Write(buffer, length);
}
status_t
Protocol::ProcessCommand(Command& command, bigtime_t timeout)
{
BString commandString = command.CommandString();
if (commandString.IsEmpty())
return B_BAD_VALUE;
Handler* handler = dynamic_cast<Handler*>(&command);
if (handler != NULL && !AddHandler(*handler))
return B_NO_MEMORY;
int32 commandID = NextCommandID();
status_t status = SendCommand(commandID, commandString.String());
if (status == B_OK) {
fOngoingCommands[commandID] = &command;
status = HandleResponse(&command, timeout);
}
if (handler != NULL)
RemoveHandler(*handler);
return status;
}
status_t
Protocol::HandleResponse(Command* command, bigtime_t timeout,
bool disconnectOnTimeout)
{
status_t commandStatus = B_OK;
IMAP::ResponseParser parser(*fBufferedSocket);
if (IMAP::LiteralHandler* literalHandler
= dynamic_cast<IMAP::LiteralHandler*>(command))
parser.SetLiteralHandler(literalHandler);
IMAP::Response response;
bool done = false;
while (!done) {
try {
status_t status = parser.NextResponse(response, timeout);
if (status != B_OK) {
if (status != B_TIMED_OUT || disconnectOnTimeout)
_Disconnect();
return status;
}
if (response.IsUntagged() || response.IsContinuation()) {
bool handled = false;
for (int32 i = fHandlerList.CountItems(); i-- > 0;) {
if (fHandlerList.ItemAt(i)->HandleUntagged(response)) {
handled = true;
break;
}
}
if (!handled)
printf("Unhandled S: %s\n", response.ToString().String());
} else {
CommandIDMap::iterator found
= fOngoingCommands.find(response.Tag());
if (found != fOngoingCommands.end()) {
status_t status = found->second->HandleTagged(response);
if (status != B_OK)
commandStatus = status;
fOngoingCommands.erase(found);
} else
printf("Unknown tag S: %s\n", response.ToString().String());
}
} catch (ParseException& exception) {
printf("Error during parsing: %s\n", exception.Message());
} catch (StreamException& exception) {
return exception.Status();
}
if (fOngoingCommands.size() == 0)
done = true;
}
return commandStatus;
}
int32
Protocol::NextCommandID()
{
fCommandID++;
return fCommandID;
}
status_t
Protocol::_Disconnect()
{
fOngoingCommands.clear();
fIsConnected = false;
delete fBufferedSocket;
fBufferedSocket = NULL;
delete fSocket;
fSocket = NULL;
return B_OK;
}
status_t
Protocol::_GetAllFolders(BStringList& folders)
{
ListCommand command(NULL, false);
status_t status = ProcessCommand(command);
if (status != B_OK)
return status;
folders = command.FolderList();
return status;
}
void
Protocol::_ParseCapabilities(const ArgumentList& arguments)
{
fCapabilities.MakeEmpty();
for (int32 i = 0; i < arguments.CountItems(); i++) {
if (StringArgument* argument
= dynamic_cast<StringArgument*>(arguments.ItemAt(i)))
fCapabilities.AddItem(new StringArgument(*argument));
}
TRACE("capabilities: %s\n", fCapabilities.ToString().String());
}
}