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

View File

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

View File

@ -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());
}

View File

@ -36,39 +36,6 @@ typedef BObjectList<Handler> HandlerList;
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 {
public:
FolderInfo()
@ -144,7 +111,6 @@ private:
protected:
BSocket* fSocket;
BBufferedDataIO* fBufferedSocket;
ConnectionReader fConnectionReader;
HandlerList fHandlerList;
CommandList fAfterQuackCommands;

View File

@ -8,9 +8,6 @@
#include <stdlib.h>
#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;
}

View File

@ -8,6 +8,7 @@
#include <stdexcept>
#include <DataIO.h>
#include <ObjectList.h>
#include <String.h>
@ -16,7 +17,6 @@ namespace IMAP {
class Argument;
class ConnectionReader;
class ArgumentList : public BObjectList<Argument> {
@ -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)
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;
};