From b47bd3cf51bd9a3c0d1b779369635582d42871a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Mon, 31 Oct 2011 23:33:22 +0000 Subject: [PATCH] * Work in progress of an IMAP response parser that will replace weak and error prone parsing method that is currently utilized by the IMAP module. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@43052 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../imap/imap_lib/Response.cpp | 413 ++++++++++++++++++ .../imap/imap_lib/Response.h | 150 +++++++ 2 files changed, 563 insertions(+) create mode 100644 src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp create mode 100644 src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h 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 new file mode 100644 index 0000000000..0454883c8d --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp @@ -0,0 +1,413 @@ +/* + * Copyright 2011, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "Response.h" + + +namespace IMAP { + + +ArgumentList::ArgumentList() + : + fArguments(5, true) +{ +} + + +ArgumentList::~ArgumentList() +{ +} + + +BString +ArgumentList::StringAt(int32 index) const +{ + if (index >= 0 && index < fArguments.CountItems()) { + if (StringArgument* argument = dynamic_cast( + fArguments.ItemAt(index))) + return argument->String(); + } + return ""; +} + + +bool +ArgumentList::IsStringAt(int32 index) const +{ + if (index >= 0 && index < fArguments.CountItems()) { + if (dynamic_cast(fArguments.ItemAt(index)) != NULL) + return true; + } + return false; +} + + +bool +ArgumentList::EqualsAt(int32 index, const char* string) const +{ + return StringAt(index).ICompare(string); +} + + +const ArgumentList& +ArgumentList::ListAt(int32 index) const +{ + if (index >= 0 && index < fArguments.CountItems()) { + if (ListArgument* argument = dynamic_cast( + fArguments.ItemAt(index))) + return argument->List(); + } + + static ArgumentList empty(0, true); + return empty; +} + + +bool +ArgumentList::IsListAt(int32 index) const +{ + if (index >= 0 && index < fArguments.CountItems()) { + if (ListArgument* argument = dynamic_cast( + fArguments.ItemAt(index))) + return true; + } + return false; +} + + +bool +ArgumentList::IsListAt(int32 index, char kind) const +{ + if (index >= 0 && index < fArguments.CountItems()) { + if (ListArgument* argument = dynamic_cast( + fArguments.ItemAt(index))) + return argument->Kind() == kind; + } + return false; +} + + +int32 +ArgumentList::IntegerAt(int32 index) const +{ + return atoi(StringAt(index).String()); +} + + +bool +ArgumentList::IsIntegerAt(int32 index) const +{ + BString string = StringAt(index); + for (int32 i = 0; i < string.Length(); i++) { + if (!isdigit(string.ByteAt(i))) + return false; + } + return string.Length() > 0; +} + + +// #pragma mark - + + +Argument::Argument() +{ +} + + +Argument::~Argument() +{ +} + + +/*static*/ BString +Argument::ToString(const ArgumentList& arguments) +{ + BString string; + + for (int32 i = 0; i < arguments.CountItems(); i++) { + if (i > 0) + string += ", "; + string += arguments.ItemAt(i)->ToString(); + } + return string; +} + + +bool +Argument::Contains(const ArgumentList& arguments, const char* string) const +{ + for (int32 i = 0; i < arguments.CountItems(); i++) { + if (StringArgument* argument = dynamic_cast( + arguments.ItemAt(i))) { + if (argument->String().ICompare(string)) + return true; + } + } + return false; +} + + +// #pragma mark - + + +ListArgument::ListArgument() + : + fList(5, true) +{ +} + + +BString +ListArgument::ToString() const +{ + BString string("("); + string += Argument::ToString(response.Arguments()); + string += ")"; + + return string; +} + + +// #pragma mark - + + +StringArgument::StringArgument(const BString& string) + : + fString(string) +{ +} + + +BString +StringArgument::ToString() const +{ + return fString; +} + + +// #pragma mark - + + +ParseException::ParseException() + : + fMessage(NULL) +{ +} + + +ParseException::ParseException(const char* message) + : + fMessage(message) +{ +} + + +ParseException::~ParseException() +{ +} + + +// #pragma mark - + + +ExpectedParseException::ExpectedParseException(char expected, char instead) +{ + snprintf(fBuffer, sizeof(fBuffer), "Expected \"%c\", but got \"%c\"!", + expected, instead); + fMessage = fBuffer; +} + + +// #pragma mark - + + +Response::Response() + : + fTag(0), + fArguments(5, true), + fContinued(false) +{ +} + + +Response::~Response() +{ +} + + +void +Response::SetTo(const char* line) throw(ParseException) +{ + MakeEmpty(); + fTag = 0; + fContinued = false; + + if (line[0] == '*') { + // Untagged response + Consume(line, '*'); + Consume(line, ' '); + } else if (line[0] == '+') { + // Continuation + Consume(line, '+'); + fContinued = true; + } else { + // Tagged response + Consume(line, 'A'); + fTag = strtoul(line, (char**)&line, 10); + if (line == NULL) + ParseException("Invalid tag!"); + Consume(line, ' '); + } + + char c = ParseLine(this, line); + if (c != '\0') + throw ExpectedParseException('\0', c); +} + + +bool +Response::IsCommand(const char* command) const +{ + return IsStringAt(0, command); +} + + +char +Response::ParseLine(ArgumentList& arguments, const char*& line) +{ + while (line[0] != '\0') { + char c = line[0]; + switch (c) { + case '(': + ParseList(arguments, line, '(', ')'); + break; + case '[': + ParseList(arguments, line, '[', ']'); + break; + case ')': + case ']': + Consume(line, c); + return c; + case '"': + ParseQuoted(arguments, line); + break; + case '{': + ParseLiteral(arguments, line); + break; + + case ' ': + case '\t': + // whitespace + Consume(line, c); + break; + + case '\r': + Consume(line, '\r'); + Consume(line, '\n'); + return '\0'; + case '\n': + Consume(line, '\n'); + return '\0'; + + default: + ParseString(arguments, line); + break; + } + } + + return '\0'; +} + + +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, + char end) +{ + Consume(line, start); + + ListArgument* argument = new ListArgument(start); + arguments.AddItem(argument); + + char c = ParseLine(argument->List(), line); + if (c != end) + throw ExpectedParseException(end, c); +} + + +void +Response::ParseQuoted(ArgumentList& arguments, const char*& line) +{ + Consume(line, '"'); + + BString string; + char* output = string.LockBuffer(strlen(line)); + int32 index = 0; + + while (line[0] != '\0') { + char c = line[0]; + if (c == '\\') { + line++; + if (line[0] == '\0') + break; + } else if (c == '"') { + line++; + output[index] = '\0'; + string.UnlockBuffer(index); + arguments.AddItem(new StringArgument(string)); + return; + } + + output[index++] = c; + line++; + } + + throw ParseException("Unexpected end of qouted string!"); +} + + +void +Response::ParseLiteral(ArgumentList& arguments, const char*& line) +{ + // TODO! + throw ParseException("Literals are not yet supported!"); +} + + +void +Response::ParseString(ArgumentList& arguments, const char*& line) +{ + arguments.AddItem(new StringArgument(ExtractString(line))); +} + + +BString +Response::ExtractString(const char*& line) +{ + const char* start = line; + + while (line[0] != '\0') { + char c = line[0]; + if (c <= ' ' || strchr("()[]{}\"", c) != NULL) + return BString(start, line - start); + + line++; + } + + throw ParseException("Unexpected end of string"); +} + + +} // namespace IMAP 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 new file mode 100644 index 0000000000..f6201970c7 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.h @@ -0,0 +1,150 @@ +/* + * Copyright 2011, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef RESPONSE_H +#define RESPONSE_H + + +#include + +#include +#include + + +namespace IMAP { + + +class Argument; + + +class ArgumentList { +public: + ArgumentList(); + ~ArgumentList(); + + size_t CountItems() + { return (size_t)fArguments.CountItems(); } + Argument& ItemAt(size_t index) const + { return *fArguments.ItemAt(index); } + void MakeEmpty() + { fArguments.MakeEmpty(); } + bool AddItem(Argument* argument) + { return fArguments.AddItem(argument); } + Argument* RemoveItem(size_t index) + { return fArguments.RemoveItemAt(index); } + + BString ToString() const; + bool Contains(const char* string) const; + + BString StringAt(int32 index) const; + bool IsStringAt(int32 index) const; + bool EqualsAt(int32 index, + const char* string) const; + const ArgumentList& ListAt(int32 index) const; + bool IsListAt(int32 index) const; + bool IsListAt(int32 index, char kind) const; + int32 IntegerAt(int32 index) const; + bool IsIntegerAt(int32 index) const; + +private: + BObjectList fArguments; +}; + + +class Argument { +public: + Argument(); + virtual ~Argument(); + + virtual BString ToString() const = 0; +}; + + +class ListArgument : public Argument { +public: + ListArgument(char kind); + + ArgumentList& List() { return fList; } + char Kind() { return fKind; } + + virtual BString ToString() const; + +private: + ArgumentList fList; + char fKind; +}; + + +class StringArgument : public Argument { +public: + StringArgument(const BString& string); + + const BString& String() { return fString; } + + virtual BString ToString() const; + +private: + BString fString; +}; + + +class ParseException : public std::exception { +public: + ParseException(); + ParseException(const char* message); + virtual ~ParseException(); + + const char* Message() const { return fMessage; } + +protected: + const char* fMessage; +}; + + +class ExpectedParseException : ParseException { +public: + ExpectedParseException(char expected, + char instead); + +protected: + char fBuffer[64]; +}; + + +class Response : public ArgumentList { +public: + Response(); + ~Response(); + + void SetTo(const char* line) throw(ParseException); + + bool IsUntagged() const { return fTag == 0; } + int32 Tag() const { return fTag; } + bool IsCommand(const char* command) const; + bool IsContinued() const { return fContinued; } + +protected: + char ParseLine(ArgumentList& arguments, + const char*& line); + void Consume(const char*& line, char c); + void ParseList(ArgumentList& arguments, + const char*& line, char start, char end); + void ParseQuoted(ArgumentList& arguments, + const char*& line); + void ParseLiteral(ArgumentList& arguments, + const char*& line); + void ParseString(ArgumentList& arguments, + const char*& line); + BString ExtractString(const char*& line); + +protected: + int32 fTag; + bool fContinued; +}; + + +} // namespace IMAP + + +#endif // RESPONSE_H