From a5170470701b5d51ede5a4ec9ef60b28aff62a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Wed, 30 Nov 2011 22:17:23 +0100 Subject: [PATCH] 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). --- .../imap/imap_lib/Commands.h | 1 - .../imap/imap_lib/Protocol.cpp | 131 +------- .../imap/imap_lib/Protocol.h | 34 --- .../imap/imap_lib/Response.cpp | 284 +++++++++++------- .../imap/imap_lib/Response.h | 62 ++-- 5 files changed, 220 insertions(+), 292 deletions(-) diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h index 8a6e828d8e..5f3f66c485 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h @@ -19,7 +19,6 @@ typedef std::vector StringList; namespace IMAP { -class ConnectionReader; class HandlerListener; diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp index 76be8d92c9..b7ef6f82d1 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp @@ -23,132 +23,6 @@ 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() : fSocket(NULL), @@ -209,7 +83,6 @@ Protocol::Connect(const BNetworkAddress& address, const char* username, TRACE("Login\n"); - fConnectionReader.SetTo(*fSocket); fIsConnected = true; LoginCommand login(username, password); @@ -400,7 +273,7 @@ status_t Protocol::HandleResponse(bigtime_t timeout, bool disconnectOnTimeout) { status_t commandStatus = B_OK; - IMAP::ResponseParser parser(fConnectionReader); + IMAP::ResponseParser parser(*fBufferedSocket); IMAP::Response response; bool done = false; @@ -437,7 +310,7 @@ Protocol::HandleResponse(bigtime_t timeout, bool disconnectOnTimeout) } else printf("Unknown tag S: %s\n", response.ToString().String()); } - } catch (IMAP::ParseException& exception) { + } catch (ParseException& exception) { printf("Error during parsing: %s\n", exception.Message()); } diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.h b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.h index 1b1b9bc14c..218b8d3b90 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.h +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.h @@ -36,39 +36,6 @@ typedef BObjectList HandlerList; typedef std::map 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 { public: FolderInfo() @@ -144,7 +111,6 @@ private: protected: BSocket* fSocket; BBufferedDataIO* fBufferedSocket; - ConnectionReader fConnectionReader; HandlerList fHandlerList; CommandList fAfterQuackCommands; diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp index a49df18b24..752197239e 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp @@ -8,9 +8,6 @@ #include -#include "Protocol.h" - // TODO: remove again once the ConnectionReader is out - #define TRACE_IMAP #ifdef TRACE_IMAP @@ -206,15 +203,26 @@ StringArgument::ToString() const 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) { - snprintf(fBuffer, sizeof(fBuffer), "Expected \"%c\", but got \"%c\"!", - expected, instead); - fMessage = fBuffer; + char bufferA[8]; + char bufferB[8]; + 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() : fTag(0), - fContinuation(false) + fContinuation(false), + fHasNextChar(false) { } @@ -260,33 +279,29 @@ Response::~Response() void -Response::Parse(ConnectionReader& reader, const char* line, - LiteralHandler* handler) throw(ParseException) +Response::Parse(BDataIO& stream, LiteralHandler* handler) throw(ParseException) { MakeEmpty(); - fReader = &reader; fLiteralHandler = handler; fTag = 0; fContinuation = false; + fHasNextChar = false; - if (line[0] == '*') { + char begin = Next(stream); + if (begin == '*') { // Untagged response - Consume(line, '*'); - Consume(line, ' '); - } else if (line[0] == '+') { + Consume(stream, ' '); + } else if (begin == '+') { // Continuation - Consume(line, '+'); fContinuation = true; - } else { + } else if (begin == 'A') { // Tagged response - Consume(line, 'A'); - fTag = strtoul(line, (char**)&line, 10); - if (line == NULL) - ParseException("Invalid tag!"); - Consume(line, ' '); - } + fTag = ExtractNumber(stream); + Consume(stream, ' '); + } else + throw ParseException("Unexpected response begin"); - char c = ParseLine(*this, line); + char c = ParseLine(*this, stream); if (c != '\0') throw ExpectedParseException('\0', c); } @@ -300,44 +315,47 @@ Response::IsCommand(const char* command) const char -Response::ParseLine(ArgumentList& arguments, const char*& line) +Response::ParseLine(ArgumentList& arguments, BDataIO& stream) { - while (line[0] != '\0') { - char c = line[0]; + while (true) { + char c = Peek(stream); + if (c == '\0') + break; + switch (c) { case '(': - ParseList(arguments, line, '(', ')'); + ParseList(arguments, stream, '(', ')'); break; case '[': - ParseList(arguments, line, '[', ']'); + ParseList(arguments, stream, '[', ']'); break; case ')': case ']': - Consume(line, c); + Consume(stream, c); return c; case '"': - ParseQuoted(arguments, line); + ParseQuoted(arguments, stream); break; case '{': - ParseLiteral(arguments, line); + ParseLiteral(arguments, stream); break; case ' ': case '\t': // whitespace - Consume(line, c); + Consume(stream, c); break; case '\r': - Consume(line, '\r'); - Consume(line, '\n'); + Consume(stream, '\r'); + Consume(stream, '\n'); return '\0'; case '\n': - Consume(line, '\n'); + Consume(stream, '\n'); return '\0'; default: - ParseString(arguments, line); + ParseString(arguments, stream); break; } } @@ -347,55 +365,38 @@ Response::ParseLine(ArgumentList& arguments, const char*& line) void -Response::Consume(const char*& line, char c) -{ - if (line[0] != c) - throw ExpectedParseException(c, line[0]); - - line++; -} - - -void -Response::ParseList(ArgumentList& arguments, const char*& line, char start, +Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start, char end) { - Consume(line, start); + Consume(stream, start); ListArgument* argument = new ListArgument(start); arguments.AddItem(argument); - char c = ParseLine(argument->List(), line); + char c = ParseLine(argument->List(), stream); if (c != end) throw ExpectedParseException(end, c); } void -Response::ParseQuoted(ArgumentList& arguments, const char*& line) +Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream) { - Consume(line, '"'); + Consume(stream, '"'); BString string; - char* output = string.LockBuffer(strlen(line)); - int32 index = 0; - - while (line[0] != '\0') { - char c = line[0]; + while (true) { + char c = Next(stream); if (c == '\\') { - line++; - if (line[0] == '\0') - break; + c = Next(stream); } else if (c == '"') { - line++; - output[index] = '\0'; - string.UnlockBuffer(index); arguments.AddItem(new StringArgument(string)); return; } + if (c == '\0') + break; - output[index++] = c; - line++; + string += c; } throw ParseException("Unexpected end of qouted string!"); @@ -403,62 +404,144 @@ Response::ParseQuoted(ArgumentList& arguments, const char*& line) void -Response::ParseLiteral(ArgumentList& arguments, const char*& line) +Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream) { - Consume(line, '{'); - off_t size = atoll(ExtractString(line)); - Consume(line, '}'); - Consume(line, '\r'); - Consume(line, '\n'); + Consume(stream, '{'); + size_t size = ExtractNumber(stream); + Consume(stream, '}'); + Consume(stream, '\r'); + Consume(stream, '\n'); if (fLiteralHandler != NULL) - fLiteralHandler->HandleLiteral(*fReader, size); + fLiteralHandler->HandleLiteral(stream, size); else { - // The default implementation just throws the data away - BMallocIO stream; - TRACE("Trying to read literal with %llu bytes.\n", size); - status_t status = fReader->ReadToStream(size, stream); - if (status == B_OK) { - TRACE("LITERAL: %-*s\n", (int)size, (char*)stream.Buffer()); - } else - TRACE("Reading literal failed: %s\n", strerror(status)); + // The default implementation just adds the data as a string + TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size); + BString string; + char* buffer = string.LockBuffer(size); + if (buffer == NULL) { + throw ParseException("Not enough memory for literal of %" + B_PRIuSIZE " bytes.", size); + } + + 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 -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 -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 - while (line[0] != '\0') { - char c = line[0]; + while (true) { + char c = Peek(stream); + if (c == '\0') + break; if (c <= ' ' || strchr("()[]{}\"", c) != NULL) - return BString(start, line - start); + return string; - line++; + string += Next(stream); } 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 - -ResponseParser::ResponseParser(ConnectionReader& reader) +ResponseParser::ResponseParser(BDataIO& stream) : fLiteralHandler(NULL) { - SetTo(reader); + SetTo(stream); } @@ -468,9 +551,9 @@ ResponseParser::~ResponseParser() 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) throw(ParseException) { - BString line; - 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); - + response.Parse(*fStream, fLiteralHandler); return B_OK; } diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h index 5593fabcee..b5f1663fc1 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h @@ -8,6 +8,7 @@ #include +#include #include #include @@ -16,7 +17,6 @@ namespace IMAP { class Argument; -class ConnectionReader; class ArgumentList : public BObjectList { @@ -82,12 +82,18 @@ private: class ParseException : public std::exception { public: ParseException(); - ParseException(const char* message); + ParseException(const char* format, ...); - const char* Message() const { return fMessage; } + const char* Message() const { return fBuffer; } protected: - const char* fMessage; + char fBuffer[64]; +}; + + +class StreamException : public ParseException { +public: + StreamException(status_t status); }; @@ -97,7 +103,7 @@ public: char instead); protected: - char fBuffer[64]; + const char* CharToString(char* buffer, size_t size, char c); }; @@ -106,8 +112,8 @@ public: LiteralHandler(); virtual ~LiteralHandler(); - virtual void HandleLiteral(ConnectionReader& reader, - off_t length) = 0; + virtual void HandleLiteral(BDataIO& stream, + size_t length) = 0; }; @@ -116,50 +122,60 @@ public: Response(); ~Response(); - void Parse(ConnectionReader& reader, - const char* line, LiteralHandler* handler) - throw(ParseException); + void Parse(BDataIO& stream, LiteralHandler* handler) + throw(ParseException); bool IsUntagged() const { return fTag == 0; } - int32 Tag() const { return fTag; } + uint32 Tag() const { return fTag; } bool IsCommand(const char* command) const; bool IsContinuation() const { return fContinuation; } protected: char ParseLine(ArgumentList& arguments, - const char*& line); - void Consume(const char*& line, char c); + BDataIO& stream); void ParseList(ArgumentList& arguments, - const char*& line, char start, char end); + BDataIO& stream, char start, char end); void ParseQuoted(ArgumentList& arguments, - const char*& line); + BDataIO& stream); void ParseLiteral(ArgumentList& arguments, - const char*& line); + BDataIO& stream); void ParseString(ArgumentList& arguments, - const char*& line); - BString ExtractString(const char*& line); + BDataIO& stream); + + 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: - ConnectionReader* fReader; LiteralHandler* fLiteralHandler; - int32 fTag; + uint32 fTag; bool fContinuation; + bool fHasNextChar; + char fNextChar; }; class ResponseParser { public: - ResponseParser(ConnectionReader& reader); + ResponseParser(BDataIO& stream); ~ResponseParser(); - void SetTo(ConnectionReader& reader); + void SetTo(BDataIO& stream); void SetLiteralHandler(LiteralHandler* handler); status_t NextResponse(Response& response, bigtime_t timeout) throw(ParseException); +private: + ResponseParser(const ResponseParser& other); + protected: - ConnectionReader* fReader; + BDataIO* fStream; LiteralHandler* fLiteralHandler; };