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