Got rid of the ConnectionReader, and implemented parsing literals.

* The parser now works on a BDataIO instead. It's now even a bit simpler
  overall.
* Implemented ParseLiteral() - the default implementation just reads the data
  into a string. Thanks to BDataIO this was now very simple to do.
* Tested the code: imap_tester can now successfully read mails (using the raw
  command only, at the moment).
This commit is contained in:
Axel Dörfler 2011-11-30 22:17:23 +01:00
parent 37d26ae5e2
commit a517047070
5 changed files with 220 additions and 292 deletions
src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib

View File

@ -19,7 +19,6 @@ typedef std::vector<BString> StringList;
namespace IMAP { namespace IMAP {
class ConnectionReader;
class HandlerListener; class HandlerListener;

View File

@ -23,132 +23,6 @@
namespace IMAP { namespace IMAP {
ConnectionReader::ConnectionReader()
:
fSocket(NULL)
{
}
void
ConnectionReader::SetTo(BSocket& socket)
{
fSocket = &socket;
fBufferedSocket = new BBufferedDataIO(socket, 32768, false, true);
}
status_t
ConnectionReader::GetNextLine(BString& line, bigtime_t timeout,
int32 maxUnfinishedLine)
{
line.SetTo((const char*)NULL, 0);
while (true) {
status_t status = _GetNextDataBunch(line, timeout);
if (status == B_OK)
return status;
if (status == B_NAME_NOT_FOUND) {
if (maxUnfinishedLine < 0 || line.Length() < maxUnfinishedLine)
continue;
else
return status;
}
return status;
}
return B_ERROR;
}
status_t
ConnectionReader::FinishLine(BString& line)
{
while (true) {
status_t status = _GetNextDataBunch(line, B_INFINITE_TIMEOUT);
if (status == B_OK)
return status;
if (status == B_NAME_NOT_FOUND)
continue;
return status;
}
return B_ERROR;
}
status_t
ConnectionReader::ReadToStream(int32 size, BDataIO& out)
{
const int32 kBunchSize = 1024; // 1Kb
char buffer[kBunchSize];
int32 readSize = size - fStringBuffer.Length();
int32 readed = fStringBuffer.Length();
if (readSize < 0) {
readed = size;
}
out.Write(fStringBuffer.String(), readed);
fStringBuffer.Remove(0, readed);
while (readSize > 0) {
int32 bunchSize = readSize < kBunchSize ? readSize : kBunchSize;
int nReaded = fBufferedSocket->Read(buffer, bunchSize);
if (nReaded < 0)
return B_ERROR;
readSize -= nReaded;
out.Write(buffer, nReaded);
}
return B_OK;
}
status_t
ConnectionReader::_GetNextDataBunch(BString& line, bigtime_t timeout,
uint32 maxNewLength)
{
if (_ExtractTillEndOfLine(line))
return B_OK;
char buffer[maxNewLength];
//
// if (timeout != B_INFINITE_TIMEOUT) {
// status_t status = fSocket->WaitForReadable(timeout);
// if (status != B_OK)
// return status;
// }
int nReaded = fBufferedSocket->Read(buffer, maxNewLength);
if (nReaded <= 0)
return B_ERROR;
fStringBuffer.SetTo(buffer, nReaded);
if (_ExtractTillEndOfLine(line))
return B_OK;
return B_NAME_NOT_FOUND;
}
bool
ConnectionReader::_ExtractTillEndOfLine(BString& out)
{
int32 endPos = fStringBuffer.FindFirst('\n');
if (endPos == B_ERROR) {
endPos = fStringBuffer.FindFirst(xEOF);
if (endPos == B_ERROR) {
out += fStringBuffer;
fStringBuffer.SetTo((const char*)NULL, 0);
return false;
}
}
out.Append(fStringBuffer, endPos + 1);
fStringBuffer.Remove(0, endPos + 1);
return true;
}
// #pragma mark -
Protocol::Protocol() Protocol::Protocol()
: :
fSocket(NULL), fSocket(NULL),
@ -209,7 +83,6 @@ Protocol::Connect(const BNetworkAddress& address, const char* username,
TRACE("Login\n"); TRACE("Login\n");
fConnectionReader.SetTo(*fSocket);
fIsConnected = true; fIsConnected = true;
LoginCommand login(username, password); LoginCommand login(username, password);
@ -400,7 +273,7 @@ status_t
Protocol::HandleResponse(bigtime_t timeout, bool disconnectOnTimeout) Protocol::HandleResponse(bigtime_t timeout, bool disconnectOnTimeout)
{ {
status_t commandStatus = B_OK; status_t commandStatus = B_OK;
IMAP::ResponseParser parser(fConnectionReader); IMAP::ResponseParser parser(*fBufferedSocket);
IMAP::Response response; IMAP::Response response;
bool done = false; bool done = false;
@ -437,7 +310,7 @@ Protocol::HandleResponse(bigtime_t timeout, bool disconnectOnTimeout)
} else } else
printf("Unknown tag S: %s\n", response.ToString().String()); printf("Unknown tag S: %s\n", response.ToString().String());
} }
} catch (IMAP::ParseException& exception) { } catch (ParseException& exception) {
printf("Error during parsing: %s\n", exception.Message()); printf("Error during parsing: %s\n", exception.Message());
} }

View File

@ -36,39 +36,6 @@ typedef BObjectList<Handler> HandlerList;
typedef std::map<int32, Command*> CommandIDMap; typedef std::map<int32, Command*> CommandIDMap;
// TODO: throw this class away, and just use a BBufferedDataIO instead.
class ConnectionReader {
public:
ConnectionReader();
void SetTo(BSocket& socket);
/*! Try to read line. If no end of line is found at least
minUnfinishedLine characters are returned. */
status_t GetNextLine(BString& line,
bigtime_t timeout = kIMAP4ClientTimeout,
int32 maxUnfinishedLine = -1);
/*! Read data and append it to line till the end of file is
reached. */
status_t FinishLine(BString& line);
status_t ReadToStream(int32 size, BDataIO& out);
private:
/*! Try to read till the end of line is reached. To do so maximal
maxNewLength bytes are read from the server if needed. */
status_t _GetNextDataBunch(BString& line,
bigtime_t timeout,
uint32 maxNewLength = 256);
bool _ExtractTillEndOfLine(BString& out);
private:
BSocket* fSocket;
BBufferedDataIO* fBufferedSocket;
BString fStringBuffer;
};
class FolderInfo { class FolderInfo {
public: public:
FolderInfo() FolderInfo()
@ -144,7 +111,6 @@ private:
protected: protected:
BSocket* fSocket; BSocket* fSocket;
BBufferedDataIO* fBufferedSocket; BBufferedDataIO* fBufferedSocket;
ConnectionReader fConnectionReader;
HandlerList fHandlerList; HandlerList fHandlerList;
CommandList fAfterQuackCommands; CommandList fAfterQuackCommands;

View File

@ -8,9 +8,6 @@
#include <stdlib.h> #include <stdlib.h>
#include "Protocol.h"
// TODO: remove again once the ConnectionReader is out
#define TRACE_IMAP #define TRACE_IMAP
#ifdef TRACE_IMAP #ifdef TRACE_IMAP
@ -206,15 +203,26 @@ StringArgument::ToString() const
ParseException::ParseException() ParseException::ParseException()
:
fMessage(NULL)
{ {
fBuffer[0] = '\0';
} }
ParseException::ParseException(const char* message) ParseException::ParseException(const char* format, ...)
{
va_list args;
va_start(args, format);
vsnprintf(fBuffer, sizeof(fBuffer), format, args);
va_end(args);
}
// #pragma mark -
StreamException::StreamException(status_t status)
: :
fMessage(message) ParseException("Error from stream: %s", status)
{ {
} }
@ -224,9 +232,19 @@ ParseException::ParseException(const char* message)
ExpectedParseException::ExpectedParseException(char expected, char instead) ExpectedParseException::ExpectedParseException(char expected, char instead)
{ {
snprintf(fBuffer, sizeof(fBuffer), "Expected \"%c\", but got \"%c\"!", char bufferA[8];
expected, instead); char bufferB[8];
fMessage = fBuffer; snprintf(fBuffer, sizeof(fBuffer), "Expected %s, but got %s instead!",
CharToString(bufferA, sizeof(bufferA), expected),
CharToString(bufferB, sizeof(bufferB), instead));
}
const char*
ExpectedParseException::CharToString(char* buffer, size_t size, char c)
{
snprintf(buffer, size, isprint(c) ? "\"%c\"" : "(%x)", c);
return buffer;
} }
@ -249,7 +267,8 @@ LiteralHandler::~LiteralHandler()
Response::Response() Response::Response()
: :
fTag(0), fTag(0),
fContinuation(false) fContinuation(false),
fHasNextChar(false)
{ {
} }
@ -260,33 +279,29 @@ Response::~Response()
void void
Response::Parse(ConnectionReader& reader, const char* line, Response::Parse(BDataIO& stream, LiteralHandler* handler) throw(ParseException)
LiteralHandler* handler) throw(ParseException)
{ {
MakeEmpty(); MakeEmpty();
fReader = &reader;
fLiteralHandler = handler; fLiteralHandler = handler;
fTag = 0; fTag = 0;
fContinuation = false; fContinuation = false;
fHasNextChar = false;
if (line[0] == '*') { char begin = Next(stream);
if (begin == '*') {
// Untagged response // Untagged response
Consume(line, '*'); Consume(stream, ' ');
Consume(line, ' '); } else if (begin == '+') {
} else if (line[0] == '+') {
// Continuation // Continuation
Consume(line, '+');
fContinuation = true; fContinuation = true;
} else { } else if (begin == 'A') {
// Tagged response // Tagged response
Consume(line, 'A'); fTag = ExtractNumber(stream);
fTag = strtoul(line, (char**)&line, 10); Consume(stream, ' ');
if (line == NULL) } else
ParseException("Invalid tag!"); throw ParseException("Unexpected response begin");
Consume(line, ' ');
}
char c = ParseLine(*this, line); char c = ParseLine(*this, stream);
if (c != '\0') if (c != '\0')
throw ExpectedParseException('\0', c); throw ExpectedParseException('\0', c);
} }
@ -300,44 +315,47 @@ Response::IsCommand(const char* command) const
char char
Response::ParseLine(ArgumentList& arguments, const char*& line) Response::ParseLine(ArgumentList& arguments, BDataIO& stream)
{ {
while (line[0] != '\0') { while (true) {
char c = line[0]; char c = Peek(stream);
if (c == '\0')
break;
switch (c) { switch (c) {
case '(': case '(':
ParseList(arguments, line, '(', ')'); ParseList(arguments, stream, '(', ')');
break; break;
case '[': case '[':
ParseList(arguments, line, '[', ']'); ParseList(arguments, stream, '[', ']');
break; break;
case ')': case ')':
case ']': case ']':
Consume(line, c); Consume(stream, c);
return c; return c;
case '"': case '"':
ParseQuoted(arguments, line); ParseQuoted(arguments, stream);
break; break;
case '{': case '{':
ParseLiteral(arguments, line); ParseLiteral(arguments, stream);
break; break;
case ' ': case ' ':
case '\t': case '\t':
// whitespace // whitespace
Consume(line, c); Consume(stream, c);
break; break;
case '\r': case '\r':
Consume(line, '\r'); Consume(stream, '\r');
Consume(line, '\n'); Consume(stream, '\n');
return '\0'; return '\0';
case '\n': case '\n':
Consume(line, '\n'); Consume(stream, '\n');
return '\0'; return '\0';
default: default:
ParseString(arguments, line); ParseString(arguments, stream);
break; break;
} }
} }
@ -347,55 +365,38 @@ Response::ParseLine(ArgumentList& arguments, const char*& line)
void void
Response::Consume(const char*& line, char c) Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start,
{
if (line[0] != c)
throw ExpectedParseException(c, line[0]);
line++;
}
void
Response::ParseList(ArgumentList& arguments, const char*& line, char start,
char end) char end)
{ {
Consume(line, start); Consume(stream, start);
ListArgument* argument = new ListArgument(start); ListArgument* argument = new ListArgument(start);
arguments.AddItem(argument); arguments.AddItem(argument);
char c = ParseLine(argument->List(), line); char c = ParseLine(argument->List(), stream);
if (c != end) if (c != end)
throw ExpectedParseException(end, c); throw ExpectedParseException(end, c);
} }
void void
Response::ParseQuoted(ArgumentList& arguments, const char*& line) Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream)
{ {
Consume(line, '"'); Consume(stream, '"');
BString string; BString string;
char* output = string.LockBuffer(strlen(line)); while (true) {
int32 index = 0; char c = Next(stream);
while (line[0] != '\0') {
char c = line[0];
if (c == '\\') { if (c == '\\') {
line++; c = Next(stream);
if (line[0] == '\0')
break;
} else if (c == '"') { } else if (c == '"') {
line++;
output[index] = '\0';
string.UnlockBuffer(index);
arguments.AddItem(new StringArgument(string)); arguments.AddItem(new StringArgument(string));
return; return;
} }
if (c == '\0')
break;
output[index++] = c; string += c;
line++;
} }
throw ParseException("Unexpected end of qouted string!"); throw ParseException("Unexpected end of qouted string!");
@ -403,62 +404,144 @@ Response::ParseQuoted(ArgumentList& arguments, const char*& line)
void void
Response::ParseLiteral(ArgumentList& arguments, const char*& line) Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream)
{ {
Consume(line, '{'); Consume(stream, '{');
off_t size = atoll(ExtractString(line)); size_t size = ExtractNumber(stream);
Consume(line, '}'); Consume(stream, '}');
Consume(line, '\r'); Consume(stream, '\r');
Consume(line, '\n'); Consume(stream, '\n');
if (fLiteralHandler != NULL) if (fLiteralHandler != NULL)
fLiteralHandler->HandleLiteral(*fReader, size); fLiteralHandler->HandleLiteral(stream, size);
else { else {
// The default implementation just throws the data away // The default implementation just adds the data as a string
BMallocIO stream; TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size);
TRACE("Trying to read literal with %llu bytes.\n", size); BString string;
status_t status = fReader->ReadToStream(size, stream); char* buffer = string.LockBuffer(size);
if (status == B_OK) { if (buffer == NULL) {
TRACE("LITERAL: %-*s\n", (int)size, (char*)stream.Buffer()); throw ParseException("Not enough memory for literal of %"
} else B_PRIuSIZE " bytes.", size);
TRACE("Reading literal failed: %s\n", strerror(status)); }
size_t totalRead = 0;
while (totalRead < size) {
ssize_t bytesRead = stream.Read(buffer + totalRead,
size - totalRead);
if (bytesRead == 0)
throw ParseException("Unexpected end of literal");
if (bytesRead < 0)
throw StreamException(bytesRead);
totalRead += bytesRead;
}
string.UnlockBuffer(size);
arguments.AddItem(new StringArgument(string));
} }
} }
void void
Response::ParseString(ArgumentList& arguments, const char*& line) Response::ParseString(ArgumentList& arguments, BDataIO& stream)
{ {
arguments.AddItem(new StringArgument(ExtractString(line))); arguments.AddItem(new StringArgument(ExtractString(stream)));
} }
BString BString
Response::ExtractString(const char*& line) Response::ExtractString(BDataIO& stream)
{ {
const char* start = line; BString string;
// TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3 // TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3
while (line[0] != '\0') { while (true) {
char c = line[0]; char c = Peek(stream);
if (c == '\0')
break;
if (c <= ' ' || strchr("()[]{}\"", c) != NULL) if (c <= ' ' || strchr("()[]{}\"", c) != NULL)
return BString(start, line - start); return string;
line++; string += Next(stream);
} }
throw ParseException("Unexpected end of string"); throw ParseException("Unexpected end of string");
} }
size_t
Response::ExtractNumber(BDataIO& stream)
{
BString string = ExtractString(stream);
const char* end;
size_t number = strtoul(string.String(), (char**)&end, 10);
if (end == NULL || end[0] != '\0')
ParseException("Invalid number!");
return number;
}
void
Response::Consume(BDataIO& stream, char expected)
{
char c = Next(stream);
if (c != expected)
throw ExpectedParseException(expected, c);
}
char
Response::Next(BDataIO& stream)
{
if (fHasNextChar) {
fHasNextChar = false;
return fNextChar;
}
return Read(stream);
}
char
Response::Peek(BDataIO& stream)
{
if (fHasNextChar)
return fNextChar;
fNextChar = Read(stream);
fHasNextChar = true;
return fNextChar;
}
char
Response::Read(BDataIO& stream)
{
char c;
ssize_t bytesRead = stream.Read(&c, 1);
if (bytesRead == 1) {
printf("%c", c);
return c;
}
if (bytesRead == 0)
throw ParseException("Unexpected end of string");
throw StreamException(bytesRead);
}
// #pragma mark - // #pragma mark -
ResponseParser::ResponseParser(ConnectionReader& reader) ResponseParser::ResponseParser(BDataIO& stream)
: :
fLiteralHandler(NULL) fLiteralHandler(NULL)
{ {
SetTo(reader); SetTo(stream);
} }
@ -468,9 +551,9 @@ ResponseParser::~ResponseParser()
void void
ResponseParser::SetTo(ConnectionReader& reader) ResponseParser::SetTo(BDataIO& stream)
{ {
fReader = &reader; fStream = &stream;
} }
@ -485,16 +568,7 @@ status_t
ResponseParser::NextResponse(Response& response, bigtime_t timeout) ResponseParser::NextResponse(Response& response, bigtime_t timeout)
throw(ParseException) throw(ParseException)
{ {
BString line; response.Parse(*fStream, fLiteralHandler);
status_t status = fReader->GetNextLine(line, timeout);
if (status != B_OK) {
TRACE("S: read error %s", line.String());
return status;
}
TRACE("S: %s", line.String());
response.Parse(*fReader, line, fLiteralHandler);
return B_OK; return B_OK;
} }

View File

@ -8,6 +8,7 @@
#include <stdexcept> #include <stdexcept>
#include <DataIO.h>
#include <ObjectList.h> #include <ObjectList.h>
#include <String.h> #include <String.h>
@ -16,7 +17,6 @@ namespace IMAP {
class Argument; class Argument;
class ConnectionReader;
class ArgumentList : public BObjectList<Argument> { class ArgumentList : public BObjectList<Argument> {
@ -82,12 +82,18 @@ private:
class ParseException : public std::exception { class ParseException : public std::exception {
public: public:
ParseException(); ParseException();
ParseException(const char* message); ParseException(const char* format, ...);
const char* Message() const { return fMessage; } const char* Message() const { return fBuffer; }
protected: protected:
const char* fMessage; char fBuffer[64];
};
class StreamException : public ParseException {
public:
StreamException(status_t status);
}; };
@ -97,7 +103,7 @@ public:
char instead); char instead);
protected: protected:
char fBuffer[64]; const char* CharToString(char* buffer, size_t size, char c);
}; };
@ -106,8 +112,8 @@ public:
LiteralHandler(); LiteralHandler();
virtual ~LiteralHandler(); virtual ~LiteralHandler();
virtual void HandleLiteral(ConnectionReader& reader, virtual void HandleLiteral(BDataIO& stream,
off_t length) = 0; size_t length) = 0;
}; };
@ -116,50 +122,60 @@ public:
Response(); Response();
~Response(); ~Response();
void Parse(ConnectionReader& reader, void Parse(BDataIO& stream, LiteralHandler* handler)
const char* line, LiteralHandler* handler)
throw(ParseException); throw(ParseException);
bool IsUntagged() const { return fTag == 0; } bool IsUntagged() const { return fTag == 0; }
int32 Tag() const { return fTag; } uint32 Tag() const { return fTag; }
bool IsCommand(const char* command) const; bool IsCommand(const char* command) const;
bool IsContinuation() const { return fContinuation; } bool IsContinuation() const { return fContinuation; }
protected: protected:
char ParseLine(ArgumentList& arguments, char ParseLine(ArgumentList& arguments,
const char*& line); BDataIO& stream);
void Consume(const char*& line, char c);
void ParseList(ArgumentList& arguments, void ParseList(ArgumentList& arguments,
const char*& line, char start, char end); BDataIO& stream, char start, char end);
void ParseQuoted(ArgumentList& arguments, void ParseQuoted(ArgumentList& arguments,
const char*& line); BDataIO& stream);
void ParseLiteral(ArgumentList& arguments, void ParseLiteral(ArgumentList& arguments,
const char*& line); BDataIO& stream);
void ParseString(ArgumentList& arguments, void ParseString(ArgumentList& arguments,
const char*& line); BDataIO& stream);
BString ExtractString(const char*& line);
BString ExtractString(BDataIO& stream);
size_t ExtractNumber(BDataIO& stream);
void Consume(BDataIO& stream, char c);
char Next(BDataIO& stream);
char Peek(BDataIO& stream);
char Read(BDataIO& stream);
protected: protected:
ConnectionReader* fReader;
LiteralHandler* fLiteralHandler; LiteralHandler* fLiteralHandler;
int32 fTag; uint32 fTag;
bool fContinuation; bool fContinuation;
bool fHasNextChar;
char fNextChar;
}; };
class ResponseParser { class ResponseParser {
public: public:
ResponseParser(ConnectionReader& reader); ResponseParser(BDataIO& stream);
~ResponseParser(); ~ResponseParser();
void SetTo(ConnectionReader& reader); void SetTo(BDataIO& stream);
void SetLiteralHandler(LiteralHandler* handler); void SetLiteralHandler(LiteralHandler* handler);
status_t NextResponse(Response& response, status_t NextResponse(Response& response,
bigtime_t timeout) throw(ParseException); bigtime_t timeout) throw(ParseException);
private:
ResponseParser(const ResponseParser& other);
protected: protected:
ConnectionReader* fReader; BDataIO* fStream;
LiteralHandler* fLiteralHandler; LiteralHandler* fLiteralHandler;
}; };