diff options
Diffstat (limited to 'Swiften')
483 files changed, 17831 insertions, 0 deletions
diff --git a/Swiften/Application/Application.cpp b/Swiften/Application/Application.cpp new file mode 100644 index 0000000..52fd14c --- /dev/null +++ b/Swiften/Application/Application.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Application/Application.h" + +#include <boost/filesystem.hpp> +#include <stdlib.h> + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + +Application::Application(const String& name) : name_(name) { +} + +Application::~Application() { +} + +boost::filesystem::path Application::getSettingsFileName() const { + return getSettingsDir() / "settings"; +} + +boost::filesystem::path Application::getHomeDir() const { + // FIXME: Does this work on windows? + char* homeDirRaw = getenv("HOME"); + boost::filesystem::path homeDir(homeDirRaw); + return homeDir; +} + +} diff --git a/Swiften/Application/Application.h b/Swiften/Application/Application.h new file mode 100644 index 0000000..4900107 --- /dev/null +++ b/Swiften/Application/Application.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_Application_H +#define SWIFTEN_Application_H + +#include <boost/filesystem.hpp> + +#include "Swiften/Base/String.h" + +namespace Swift { + class ApplicationMessageDisplay; + + class Application { + public: + Application(const String& name); + virtual ~Application(); + + boost::filesystem::path getSettingsFileName() const; + boost::filesystem::path getHomeDir() const; + virtual boost::filesystem::path getSettingsDir() const = 0; + + const String& getName() const { + return name_; + } + + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() = 0; + + private: + String name_; + }; +} + +#endif diff --git a/Swiften/Application/ApplicationMessageDisplay.cpp b/Swiften/Application/ApplicationMessageDisplay.cpp new file mode 100644 index 0000000..48db37d --- /dev/null +++ b/Swiften/Application/ApplicationMessageDisplay.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + +ApplicationMessageDisplay::~ApplicationMessageDisplay() { +} + +} diff --git a/Swiften/Application/ApplicationMessageDisplay.h b/Swiften/Application/ApplicationMessageDisplay.h new file mode 100644 index 0000000..df7f0cb --- /dev/null +++ b/Swiften/Application/ApplicationMessageDisplay.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_ApplicationMessageDisplay_H +#define SWIFTEN_ApplicationMessageDisplay_H + +namespace Swift { + class String; + + class ApplicationMessageDisplay { + public: + virtual ~ApplicationMessageDisplay(); + + virtual void setMessage(const String& message) = 0; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplication.cpp b/Swiften/Application/MacOSX/MacOSXApplication.cpp new file mode 100644 index 0000000..1df2bfa --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplication.cpp @@ -0,0 +1,18 @@ +#include "Swiften/Application/MacOSX/MacOSXApplication.h" + +namespace Swift { + +MacOSXApplication::MacOSXApplication(const String& name) : Application(name) { +} + +ApplicationMessageDisplay* MacOSXApplication::getApplicationMessageDisplay() { + return &messageDisplay_; +} + +boost::filesystem::path MacOSXApplication::getSettingsDir() const { + boost::filesystem::path result(getHomeDir() / "Library/Application Support" / getName().getUTF8String()); + boost::filesystem::create_directory(result); + return result; +} + +} diff --git a/Swiften/Application/MacOSX/MacOSXApplication.h b/Swiften/Application/MacOSX/MacOSXApplication.h new file mode 100644 index 0000000..9e77c54 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplication.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_MacOSXApplication_H +#define SWIFTEN_MacOSXApplication_H + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h" + +namespace Swift { + class ApplicationMessageDisplay; + + class MacOSXApplication : public Application { + public: + MacOSXApplication(const String& name); + + virtual ApplicationMessageDisplay* getApplicationMessageDisplay(); + boost::filesystem::path getSettingsDir() const; + + private: + MacOSXApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h new file mode 100644 index 0000000..db551eb --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_MacOSXApplicationInitializer_H +#define SWIFTEN_MacOSXApplicationInitializer_H + +namespace Swift { + class MacOSXApplicationInitializer { + public: + MacOSXApplicationInitializer(); + ~MacOSXApplicationInitializer(); + + private: + class Private; + Private* d; + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm new file mode 100644 index 0000000..e401697 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm @@ -0,0 +1,24 @@ +#include "Swiften/Application/MacOSX/MacOSXApplicationInitializer.h" + +#include <AppKit/AppKit.h> +#include <Cocoa/Cocoa.h> + +namespace Swift { + +class MacOSXApplicationInitializer::Private { + public: + NSAutoreleasePool* autoReleasePool_; +}; + +MacOSXApplicationInitializer::MacOSXApplicationInitializer() { + d = new MacOSXApplicationInitializer::Private(); + NSApplicationLoad(); + d->autoReleasePool_ = [[NSAutoreleasePool alloc] init]; +} + +MacOSXApplicationInitializer::~MacOSXApplicationInitializer() { + [d->autoReleasePool_ release]; + delete d; +} + +} diff --git a/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h new file mode 100644 index 0000000..98af1fa --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_MacOSXApplicationMessageDisplay_H +#define SWIFTEN_MacOSXApplicationMessageDisplay_H + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + class String; + + class MacOSXApplicationMessageDisplay : public ApplicationMessageDisplay { + public: + MacOSXApplicationMessageDisplay(); + + void setMessage(const String& label); + }; +} + +#endif diff --git a/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm new file mode 100644 index 0000000..c10c707 --- /dev/null +++ b/Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm @@ -0,0 +1,20 @@ +#include "Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.h" + +#include <AppKit/AppKit.h> +#include <Cocoa/Cocoa.h> + +#include "Swiften/Base/String.h" + +namespace Swift { + +MacOSXApplicationMessageDisplay::MacOSXApplicationMessageDisplay() { +} + +void MacOSXApplicationMessageDisplay::setMessage(const String& label) { + NSString *labelString = [[NSString alloc] initWithUTF8String: label.getUTF8Data()]; + [[NSApp dockTile] setBadgeLabel: labelString]; + [labelString release]; + [NSApp requestUserAttention: NSInformationalRequest]; +} + +} diff --git a/Swiften/Application/MacOSX/Makefile.inc b/Swiften/Application/MacOSX/Makefile.inc new file mode 100644 index 0000000..9443267 --- /dev/null +++ b/Swiften/Application/MacOSX/Makefile.inc @@ -0,0 +1,6 @@ +SWIFTEN_OBJECTIVE_SOURCES += \ + Swiften/Application/MacOSX/MacOSXApplicationInitializer.mm \ + Swiften/Application/MacOSX/MacOSXApplicationMessageDisplay.mm + +SWIFTEN_SOURCES += \ + Swiften/Application/MacOSX/MacOSXApplication.cpp diff --git a/Swiften/Application/Makefile.inc b/Swiften/Application/Makefile.inc new file mode 100644 index 0000000..7cc7fcb --- /dev/null +++ b/Swiften/Application/Makefile.inc @@ -0,0 +1,9 @@ +SWIFTEN_SOURCES += \ + Swiften/Application/Application.cpp \ + Swiften/Application/ApplicationMessageDisplay.cpp + +ifeq ($(MACOSX),1) +include Swiften/Application/MacOSX/Makefile.inc +endif + +include Swiften/Application/UnitTest/Makefile.inc diff --git a/Swiften/Application/NullApplicationMessageDisplay.h b/Swiften/Application/NullApplicationMessageDisplay.h new file mode 100644 index 0000000..03e0b42 --- /dev/null +++ b/Swiften/Application/NullApplicationMessageDisplay.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_NullApplicationMessageDisplay_H +#define SWIFTEN_NullApplicationMessageDisplay_H + +#include "Swiften/Application/ApplicationMessageDisplay.h" + +namespace Swift { + class NullApplicationMessageDisplay : public ApplicationMessageDisplay { + public: + NullApplicationMessageDisplay() {} + + virtual void setMessage(const String&) { + } + }; +} + +#endif diff --git a/Swiften/Application/Platform/PlatformApplication.h b/Swiften/Application/Platform/PlatformApplication.h new file mode 100644 index 0000000..749bce4 --- /dev/null +++ b/Swiften/Application/Platform/PlatformApplication.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_PlatformApplication_H +#define SWIFTEN_PlatformApplication_H + +#include "Swiften/Base/Platform.h" + + +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include "Swiften/Application/MacOSX/MacOSXApplication.h" +namespace Swift { + typedef MacOSXApplication PlatformApplication; +} +#elif defined(SWIFTEN_PLATFORM_WIN32) +#include "Swiften/Application/Windows/WindowsApplication.h" +namespace Swift { + typedef WindowsApplication PlatformApplication; +} +#else +#include "Swiften/Application/Unix/UnixApplication.h" +namespace Swift { + typedef UnixApplication PlatformApplication; +} +#endif + +#endif diff --git a/Swiften/Application/UnitTest/ApplicationTest.cpp b/Swiften/Application/UnitTest/ApplicationTest.cpp new file mode 100644 index 0000000..e13c94b --- /dev/null +++ b/Swiften/Application/UnitTest/ApplicationTest.cpp @@ -0,0 +1,39 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/Platform/PlatformApplication.h" + +using namespace Swift; + +class ApplicationTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ApplicationTest); + CPPUNIT_TEST(testGetSettingsDir); + CPPUNIT_TEST_SUITE_END(); + + public: + ApplicationTest() {} + + void setUp() { + testling_ = new PlatformApplication("SwiftTest"); + } + + void tearDown() { + delete testling_; + } + + void testGetSettingsDir() { + boost::filesystem::path dir = testling_->getSettingsDir(); + + CPPUNIT_ASSERT(boost::filesystem::exists(dir)); + CPPUNIT_ASSERT(boost::filesystem::is_directory(dir)); + + boost::filesystem::remove(dir); + } + + private: + Application* testling_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ApplicationTest); diff --git a/Swiften/Application/UnitTest/Makefile.inc b/Swiften/Application/UnitTest/Makefile.inc new file mode 100644 index 0000000..47c8691 --- /dev/null +++ b/Swiften/Application/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Application/UnitTest/ApplicationTest.cpp diff --git a/Swiften/Application/Unix/UnixApplication.h b/Swiften/Application/Unix/UnixApplication.h new file mode 100644 index 0000000..8cf6feb --- /dev/null +++ b/Swiften/Application/Unix/UnixApplication.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_UnixApplication_H +#define SWIFTEN_UnixApplication_H + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/NullApplicationMessageDisplay.h" + +namespace Swift { + class UnixApplication : public Application { + public: + UnixApplication(const String& name) : Application(name) { + } + + private: + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() { + return &messageDisplay_; + } + + boost::filesystem::path getSettingsDir() const { + boost::filesystem::path result(getHomeDir() / ("." + getName().getUTF8String())); + boost::filesystem::create_directory(result); + return result; + } + + private: + NullApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Application/Windows/WindowsApplication.h b/Swiften/Application/Windows/WindowsApplication.h new file mode 100644 index 0000000..0fc4b12 --- /dev/null +++ b/Swiften/Application/Windows/WindowsApplication.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_WindowsApplication_H +#define SWIFTEN_WindowsApplication_H + +#include <cassert> + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/NullApplicationMessageDisplay.h" + +namespace Swift { + class WindowsApplication : public Application { + public: + WindowsApplication(const String& name) : Application(name) { + } + + protected: + virtual ApplicationMessageDisplay* getApplicationMessageDisplay() { + return &messageDisplay_; + } + + boost::filesystem::path getSettingsDir() const { + assert(false); + // FIXME: This is wrong + boost::filesystem::path result(getHomeDir() / ("." + getName().getUTF8String())); + boost::filesystem::create_directory(result); + return result; + } + + private: + NullApplicationMessageDisplay messageDisplay_; + }; +} + +#endif diff --git a/Swiften/Base/ByteArray.cpp b/Swiften/Base/ByteArray.cpp new file mode 100644 index 0000000..b3a5d8d --- /dev/null +++ b/Swiften/Base/ByteArray.cpp @@ -0,0 +1,34 @@ +#include "Swiften/Base/ByteArray.h" + +#include <fstream> + +std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s) { + std::ios::fmtflags oldFlags = os.flags(); + os << std::hex; + for (Swift::ByteArray::const_iterator i = s.begin(); i != s.end(); ++i) { + os << "0x" << static_cast<unsigned int>(static_cast<unsigned char>(*i)); + if (i + 1 < s.end()) { + os << " "; + } + } + os << std::endl; + os.flags(oldFlags); + return os; +} + +namespace Swift { + +static const int BUFFER_SIZE = 4096; + +void ByteArray::readFromFile(const String& file) { + std::ifstream input(file.getUTF8Data(), std::ios_base::in|std::ios_base::binary); + while (input.good()) { + size_t oldSize = data_.size(); + data_.resize(oldSize + BUFFER_SIZE); + input.read(&data_[oldSize], BUFFER_SIZE); + data_.resize(oldSize + input.gcount()); + } + input.close(); +} + +} diff --git a/Swiften/Base/ByteArray.h b/Swiften/Base/ByteArray.h new file mode 100644 index 0000000..88e3fae --- /dev/null +++ b/Swiften/Base/ByteArray.h @@ -0,0 +1,92 @@ +#ifndef SWIFTEN_BYTEARRAY_H +#define SWIFTEN_BYTEARRAY_H + +#include <cstring> +#include <vector> +#include <iostream> + +#include "Swiften/Base/String.h" + +namespace Swift { + class ByteArray + { + public: + typedef std::vector<char>::const_iterator const_iterator; + + ByteArray() : data_() {} + + ByteArray(const String& s) : data_(s.getUTF8String().begin(), s.getUTF8String().end()) {} + + ByteArray(const char* c) { + while (*c) { + data_.push_back(*c); + ++c; + } + } + + ByteArray(const char* c, size_t n) { + data_.resize(n); + memcpy(&data_[0], c, n); + } + + const char* getData() const { + return &data_[0]; + } + + char* getData() { + return &data_[0]; + } + + size_t getSize() const { + return data_.size(); + } + + bool isEmpty() const { + return data_.empty(); + } + + void resize(size_t size) { + return data_.resize(size); + } + + friend ByteArray operator+(const ByteArray& a, const ByteArray&b) { + ByteArray result(a); + result.data_.insert(result.data_.end(), b.data_.begin(), b.data_.end()); + return result; + } + + friend bool operator==(const ByteArray& a, const ByteArray& b) { + return a.data_ == b.data_; + } + + + const char& operator[](size_t i) const { + return data_[i]; + } + + char& operator[](size_t i) { + return data_[i]; + } + + const_iterator begin() const { + return data_.begin(); + } + + const_iterator end() const { + return data_.end(); + } + + String toString() const { + return String(getData(), getSize()); + } + + void readFromFile(const String& file); + + private: + std::vector<char> data_; + }; +} + +std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s); + +#endif diff --git a/Swiften/Base/IDGenerator.cpp b/Swiften/Base/IDGenerator.cpp new file mode 100644 index 0000000..07ead43 --- /dev/null +++ b/Swiften/Base/IDGenerator.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Base/IDGenerator.h" + +namespace Swift { + +IDGenerator::IDGenerator() { +} + +String IDGenerator::generateID() { + bool carry = true; + size_t i = 0; + while (carry && i < currentID_.getUTF8Size()) { + char c = currentID_.getUTF8String()[i]; + if (c >= 'z') { + currentID_.getUTF8String()[i] = 'a'; + } + else { + currentID_.getUTF8String()[i] = c+1; + carry = false; + } + ++i; + } + if (carry) { + currentID_ += 'a'; + } + return currentID_; +} + +} diff --git a/Swiften/Base/IDGenerator.h b/Swiften/Base/IDGenerator.h new file mode 100644 index 0000000..db7b80d --- /dev/null +++ b/Swiften/Base/IDGenerator.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_IDGenerator_H +#define SWIFTEN_IDGenerator_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class IDGenerator { + public: + IDGenerator(); + + String generateID(); + + private: + String currentID_; + }; +} + +#endif diff --git a/Swiften/Base/Makefile.inc b/Swiften/Base/Makefile.inc new file mode 100644 index 0000000..cf42910 --- /dev/null +++ b/Swiften/Base/Makefile.inc @@ -0,0 +1,7 @@ +SWIFTEN_SOURCES += \ + Swiften/Base/String.cpp \ + Swiften/Base/ByteArray.cpp \ + Swiften/Base/IDGenerator.cpp \ + Swiften/Base/sleep.cpp + +include Swiften/Base/UnitTest/Makefile.inc diff --git a/Swiften/Base/Platform.h b/Swiften/Base/Platform.h new file mode 100644 index 0000000..9e4c398 --- /dev/null +++ b/Swiften/Base/Platform.h @@ -0,0 +1,44 @@ +#ifndef SWIFTEN_Platform_H +#define SWIFTEN_Platform_H + +// Base platforms +#if defined(linux) || defined(__linux) || defined(__linux__) +#define SWIFTEN_PLATFORM_LINUX +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#define SWIFTEN_PLATFORM_BSD +#elif defined(sun) || defined(__sun) +#define SWIFTEN_PLATFORM_SOLARIS +#elif defined(__sgi) +#define SWIFTEN_PLATFORM_SGI +#elif defined(__hpux) +#define SWIFTEN_PLATFORM_HPUX +#elif defined(__CYGWIN__) +#define SWIFTEN_PLATFORM_CYGWIN +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#define SWIFTEN_PLATFORM_WIN32 +#elif defined(__BEOS__) +#define SWIFTEN_PLATFORM_BEOS +#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) +#define SWIFTEN_PLATFORM_MACOSX +#elif defined(__IBMCPP__) || defined(_AIX) +#define SWIFTEN_PLATFORM_AIX +#elif defined(__amigaos__) +#define SWIFTEN_PLATFORM_AMIGAOS +#elif defined(__QNXNTO__) +#define SWIFTEN_PLATFORM_QNX +#endif + +// Derived platforms +#if defined(SWIFTEN_PLATFORM_CYGWIN) || defined(SWIFTEN_PLATFORM_WIN32) +#define SWIFTEN_PLATFORM_WINDOWS +#endif + +// Endianness +#include <boost/detail/endian.hpp> +#if defined(BOOST_LITTLE_ENDIAN) +#define SWIFTEN_LITTLE_ENDIAN +#elif defined(BOOST_BIG_ENDIAN) +#define SWIFTEN_BIG_ENDIAN +#endif + +#endif diff --git a/Swiften/Base/String.cpp b/Swiften/Base/String.cpp new file mode 100644 index 0000000..88692b7 --- /dev/null +++ b/Swiften/Base/String.cpp @@ -0,0 +1,93 @@ +#include <cassert> + +#include "Swiften/Base/String.h" + +namespace Swift { + +static inline size_t sequenceLength(char firstByte) { + if ((firstByte & 0x80) == 0) { + return 1; + } + if ((firstByte & 0xE0) == 0xC0) { + return 2; + } + if ((firstByte & 0xF0) == 0xE0) { + return 3; + } + if ((firstByte & 0xF8) == 0xF0) { + return 4; + } + if ((firstByte & 0xFC) == 0xF8) { + return 5; + } + if ((firstByte & 0xFE) == 0xFC) { + return 6; + } + assert(false); + return 1; +} + +std::vector<unsigned int> String::getUnicodeCodePoints() const { + std::vector<unsigned int> result; + for (size_t i = 0; i < data_.size();) { + unsigned int codePoint = 0; + char firstChar = data_[i]; + size_t length = sequenceLength(firstChar); + + // First character is special + size_t firstCharBitSize = 7 - length; + if (length == 1) { + firstCharBitSize = 7; + } + codePoint = firstChar & ((1<<(firstCharBitSize+1)) - 1); + + for (size_t j = 1; j < length; ++j) { + codePoint = (codePoint<<6) | (data_[i+j] & 0x3F); + } + result.push_back(codePoint); + i += length; + } + return result; +} + + +std::pair<String,String> String::getSplittedAtFirst(char c) const { + assert((c & 0x80) == 0); + size_t firstMatch = data_.find(c); + if (firstMatch != data_.npos) { + return std::make_pair(data_.substr(0,firstMatch),data_.substr(firstMatch+1,data_.npos)); + } + else { + return std::make_pair(*this, ""); + } +} + +size_t String::getLength() const { + size_t size = 0, current = 0, end = data_.size(); + while (current < end) { + size++; + current += sequenceLength(data_[current]); + } + return size; +} + +void String::removeAll(char c) { + size_t lastPos = 0; + size_t matchingIndex = 0; + while ((matchingIndex = data_.find(c, lastPos)) != data_.npos) { + data_.erase(matchingIndex, 1); + lastPos = matchingIndex; + } +} + +void String::replaceAll(char c, const String& s) { + size_t lastPos = 0; + size_t matchingIndex = 0; + while ((matchingIndex = data_.find(c, lastPos)) != data_.npos) { + data_.replace(matchingIndex, 1, s.data_); + lastPos = matchingIndex + s.data_.size(); + } +} + + +} diff --git a/Swiften/Base/String.h b/Swiften/Base/String.h new file mode 100644 index 0000000..0bc79bf --- /dev/null +++ b/Swiften/Base/String.h @@ -0,0 +1,115 @@ +#ifndef SWIFTEN_STRING_H +#define SWIFTEN_STRING_H + +#include <ostream> +#include <string> +#include <utility> +#include <vector> +#include <cassert> + +#define SWIFTEN_STRING_TO_CFSTRING(a) \ + CFStringCreateWithBytes(NULL, reinterpret_cast<const UInt8*>(a.getUTF8Data()), a.getUTF8Size(), kCFStringEncodingUTF8, false) + +namespace Swift { + class ByteArray; + + class String { + friend class ByteArray; + + public: + String() {} + String(const char* data) : data_(data) {} + String(const char* data, size_t len) : data_(data, len) {} + String(const std::string& data) : data_(data) {} + + bool isEmpty() const { return data_.empty(); } + + const char* getUTF8Data() const { return data_.c_str(); } + const std::string& getUTF8String() const { return data_; } + std::string& getUTF8String() { return data_; } + size_t getUTF8Size() const { return data_.size(); } + std::vector<unsigned int> getUnicodeCodePoints() const; + + /** + * Returns the part before and after 'c'. + * If the given splitter does not occur in the string, the second + * component is the empty string. + */ + std::pair<String,String> getSplittedAtFirst(char c) const; + + size_t getLength() const; + + void removeAll(char c); + + void replaceAll(char c, const String& s); + + bool beginsWith(char c) const { + return data_.size() > 0 && data_[0] == c; + } + + bool beginsWith(const String& s) const { + return data_.substr(0, s.data_.size()) == s; + } + + bool endsWith(char c) const { + return data_.size() > 0 && data_[data_.size()-1] == c; + } + + String getSubstring(size_t begin, size_t end) const { + return String(data_.substr(begin, end)); + } + + size_t find(char c) const { + assert((c & 0x80) == 0); + return data_.find(c); + } + + size_t npos() const { + return data_.npos; + } + + friend String operator+(const String& a, const String& b) { + return String(a.data_ + b.data_); + } + + friend String operator+(const String& a, char b) { + return String(a.data_ + b); + } + + String& operator+=(const String& o) { + data_ += o.data_; + return *this; + } + + String& operator+=(char c) { + data_ += c; + return *this; + } + + friend bool operator>(const String& a, const String& b) { + return a.data_ > b.data_; + } + + friend bool operator<(const String& a, const String& b) { + return a.data_ < b.data_; + } + + friend bool operator!=(const String& a, const String& b) { + return a.data_ != b.data_; + } + + friend bool operator==(const String& a, const String& b) { + return a.data_ == b.data_; + } + + friend std::ostream& operator<<(std::ostream& os, const String& s) { + os << s.data_; + return os; + } + + private: + std::string data_; + }; +} + +#endif diff --git a/Swiften/Base/UnitTest/IDGeneratorTest.cpp b/Swiften/Base/UnitTest/IDGeneratorTest.cpp new file mode 100644 index 0000000..bd96d91 --- /dev/null +++ b/Swiften/Base/UnitTest/IDGeneratorTest.cpp @@ -0,0 +1,34 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <set> + +#include "Swiften/Base/IDGenerator.h" + +using namespace Swift; + +class IDGeneratorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IDGeneratorTest); + CPPUNIT_TEST(testGenerate); + CPPUNIT_TEST_SUITE_END(); + + public: + IDGeneratorTest() {} + + void setUp() { + generatedIDs_.clear(); + } + + void testGenerate() { + IDGenerator testling; + for (unsigned int i = 0; i < 26*4; ++i) { + String id = testling.generateID(); + CPPUNIT_ASSERT(generatedIDs_.insert(id).second); + } + } + + private: + std::set<String> generatedIDs_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IDGeneratorTest); diff --git a/Swiften/Base/UnitTest/Makefile.inc b/Swiften/Base/UnitTest/Makefile.inc new file mode 100644 index 0000000..f724330 --- /dev/null +++ b/Swiften/Base/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/Base/UnitTest/StringTest.cpp \ + Swiften/Base/UnitTest/IDGeneratorTest.cpp diff --git a/Swiften/Base/UnitTest/StringTest.cpp b/Swiften/Base/UnitTest/StringTest.cpp new file mode 100644 index 0000000..0b7d207 --- /dev/null +++ b/Swiften/Base/UnitTest/StringTest.cpp @@ -0,0 +1,146 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/String.h" + +using namespace Swift; + +class StringTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StringTest); + CPPUNIT_TEST(testGetLength); + CPPUNIT_TEST(testGetLength_EncodedLength2); + CPPUNIT_TEST(testGetLength_EncodedLength3); + CPPUNIT_TEST(testGetLength_EncodedLength4); + CPPUNIT_TEST(testGetUnicodeCodePoints); + CPPUNIT_TEST(testGetSplittedAtFirst); + CPPUNIT_TEST(testGetSplittedAtFirst_CharacterAtEnd); + CPPUNIT_TEST(testGetSplittedAtFirst_NoSuchCharacter); + CPPUNIT_TEST(testRemoveAll); + CPPUNIT_TEST(testRemoveAll_LastChar); + CPPUNIT_TEST(testRemoveAll_ConsecutiveChars); + CPPUNIT_TEST(testReplaceAll); + CPPUNIT_TEST(testReplaceAll_LastChar); + CPPUNIT_TEST(testReplaceAll_ConsecutiveChars); + CPPUNIT_TEST(testReplaceAll_MatchingReplace); + CPPUNIT_TEST_SUITE_END(); + + public: + StringTest() {} + + void testGetLength() { + String testling("xyz$xyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength2() { + String testling("xyz\xC2\xA2xyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength3() { + String testling("xyz\xE2\x82\xACxyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetLength_EncodedLength4() { + String testling("xyz\xf4\x8a\xaf\x8dxyz"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), testling.getLength()); + } + + void testGetUnicodeCodePoints() { + String testling("$\xc2\xa2\xe2\x82\xac\xf4\x8a\xaf\x8d"); + std::vector<unsigned int> points = testling.getUnicodeCodePoints(); + + CPPUNIT_ASSERT_EQUAL(0x24U, points[0]); + CPPUNIT_ASSERT_EQUAL(0xA2U, points[1]); + CPPUNIT_ASSERT_EQUAL(0x20ACU, points[2]); + CPPUNIT_ASSERT_EQUAL(0x10ABCDU, points[3]); + } + + void testGetSplittedAtFirst() { + String testling("ab@cd@ef"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT_EQUAL(String("cd@ef"), result.second); + } + + void testGetSplittedAtFirst_CharacterAtEnd() { + String testling("ab@"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT(result.second.isEmpty()); + } + + void testGetSplittedAtFirst_NoSuchCharacter() { + String testling("ab"); + + std::pair<String,String> result = testling.getSplittedAtFirst('@'); + CPPUNIT_ASSERT_EQUAL(String("ab"), result.first); + CPPUNIT_ASSERT(result.second.isEmpty()); + } + + void testRemoveAll() { + String testling("ab c de"); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testRemoveAll_LastChar() { + String testling("abcde "); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testRemoveAll_ConsecutiveChars() { + String testling("ab cde"); + + testling.removeAll(' '); + + CPPUNIT_ASSERT_EQUAL(String("abcde"), testling); + } + + void testReplaceAll() { + String testling("abcbd"); + + testling.replaceAll('b', "xyz"); + + CPPUNIT_ASSERT_EQUAL(String("axyzcxyzd"), testling); + } + + void testReplaceAll_LastChar() { + String testling("abc"); + + testling.replaceAll('c', "xyz"); + + CPPUNIT_ASSERT_EQUAL(String("abxyz"), testling); + } + + void testReplaceAll_ConsecutiveChars() { + String testling("abbc"); + + testling.replaceAll('b',"xyz"); + + CPPUNIT_ASSERT_EQUAL(String("axyzxyzc"), testling); + } + + void testReplaceAll_MatchingReplace() { + String testling("abc"); + + testling.replaceAll('b',"bbb"); + + CPPUNIT_ASSERT_EQUAL(String("abbbc"), testling); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StringTest); diff --git a/Swiften/Base/foreach.h b/Swiften/Base/foreach.h new file mode 100644 index 0000000..b2bee66 --- /dev/null +++ b/Swiften/Base/foreach.h @@ -0,0 +1,9 @@ +#ifndef SWIFTEN_FOREACH_H +#define SWIFTEN_FOREACH_H + +#include <boost/foreach.hpp> + +#undef foreach +#define foreach BOOST_FOREACH + +#endif diff --git a/Swiften/Base/sleep.cpp b/Swiften/Base/sleep.cpp new file mode 100644 index 0000000..cecfd72 --- /dev/null +++ b/Swiften/Base/sleep.cpp @@ -0,0 +1,14 @@ +#include "Swiften/Base/sleep.h" + +#include <boost/thread.hpp> + +namespace Swift { + +void sleep(unsigned int msecs) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.nsec += msecs*1000000; + boost::thread::sleep(xt); +} + +} diff --git a/Swiften/Base/sleep.h b/Swiften/Base/sleep.h new file mode 100644 index 0000000..b2a4ef1 --- /dev/null +++ b/Swiften/Base/sleep.h @@ -0,0 +1,8 @@ +#ifndef SWIFTEN_sleep_H +#define SWIFTEN_sleep_H + +namespace Swift { + void sleep(unsigned int msecs); +} + +#endif diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp new file mode 100644 index 0000000..e5bbf9d --- /dev/null +++ b/Swiften/Client/Client.cpp @@ -0,0 +1,147 @@ +#include "Swiften/Client/Client.h" + +#include <boost/bind.hpp> + +#include "Swiften/Client/Session.h" +#include "Swiften/StreamStack/PlatformTLSLayerFactory.h" +#include "Swiften/Network/BoostConnectionFactory.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +namespace Swift { + +Client::Client(const JID& jid, const String& password) : + IQRouter(this), jid_(jid), password_(password), session_(0) { + connectionFactory_ = new BoostConnectionFactory(); + tlsLayerFactory_ = new PlatformTLSLayerFactory(); +} + +Client::~Client() { + delete session_; + delete tlsLayerFactory_; + delete connectionFactory_; +} + +void Client::connect() { + delete session_; + session_ = new Session(jid_, connectionFactory_, tlsLayerFactory_, &payloadParserFactories_, &payloadSerializers_); + if (!certificate_.isEmpty()) { + session_->setCertificate(PKCS12Certificate(certificate_, password_)); + } + session_->onSessionStarted.connect(boost::bind(boost::ref(onConnected))); + session_->onError.connect(boost::bind(&Client::handleSessionError, this, _1)); + session_->onNeedCredentials.connect(boost::bind(&Client::handleNeedCredentials, this)); + session_->onDataRead.connect(boost::bind(&Client::handleDataRead, this, _1)); + session_->onDataWritten.connect(boost::bind(&Client::handleDataWritten, this, _1)); + session_->onElementReceived.connect(boost::bind(&Client::handleElement, this, _1)); + session_->start(); +} + +void Client::disconnect() { + if (session_) { + session_->stop(); + } +} + +void Client::send(boost::shared_ptr<Stanza> stanza) { + session_->sendElement(stanza); +} + +void Client::sendIQ(boost::shared_ptr<IQ> iq) { + send(iq); +} + +void Client::sendMessage(boost::shared_ptr<Message> message) { + send(message); +} + +void Client::sendPresence(boost::shared_ptr<Presence> presence) { + send(presence); +} + +String Client::getNewIQID() { + return idGenerator_.generateID(); +} + +void Client::handleElement(boost::shared_ptr<Element> element) { + boost::shared_ptr<Message> message = boost::dynamic_pointer_cast<Message>(element); + if (message) { + onMessageReceived(message); + return; + } + + boost::shared_ptr<Presence> presence = boost::dynamic_pointer_cast<Presence>(element); + if (presence) { + onPresenceReceived(presence); + return; + } + + boost::shared_ptr<IQ> iq = boost::dynamic_pointer_cast<IQ>(element); + if (iq) { + onIQReceived(iq); + return; + } +} + +void Client::setCertificate(const String& certificate) { + certificate_ = certificate; +} + +void Client::handleSessionError(Session::SessionError error) { + ClientError clientError; + switch (error) { + case Session::NoError: + assert(false); + break; + case Session::DomainNameResolveError: + clientError = ClientError(ClientError::DomainNameResolveError); + break; + case Session::ConnectionError: + clientError = ClientError(ClientError::ConnectionError); + break; + case Session::ConnectionReadError: + clientError = ClientError(ClientError::ConnectionReadError); + break; + case Session::XMLError: + clientError = ClientError(ClientError::XMLError); + break; + case Session::AuthenticationFailedError: + clientError = ClientError(ClientError::AuthenticationFailedError); + break; + case Session::NoSupportedAuthMechanismsError: + clientError = ClientError(ClientError::NoSupportedAuthMechanismsError); + break; + case Session::UnexpectedElementError: + clientError = ClientError(ClientError::UnexpectedElementError); + break; + case Session::ResourceBindError: + clientError = ClientError(ClientError::ResourceBindError); + break; + case Session::SessionStartError: + clientError = ClientError(ClientError::SessionStartError); + break; + case Session::TLSError: + clientError = ClientError(ClientError::TLSError); + break; + case Session::ClientCertificateLoadError: + clientError = ClientError(ClientError::ClientCertificateLoadError); + break; + case Session::ClientCertificateError: + clientError = ClientError(ClientError::ClientCertificateError); + break; + } + onError(clientError); +} + +void Client::handleNeedCredentials() { + session_->sendCredentials(password_); +} + +void Client::handleDataRead(const ByteArray& data) { + onDataRead(String(data.getData(), data.getSize())); +} + +void Client::handleDataWritten(const ByteArray& data) { + onDataWritten(String(data.getData(), data.getSize())); +} + +} diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h new file mode 100644 index 0000000..946bdbd --- /dev/null +++ b/Swiften/Client/Client.h @@ -0,0 +1,66 @@ +#ifndef SWIFTEN_Client_H +#define SWIFTEN_Client_H + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Client/Session.h" +#include "Swiften/Client/ClientError.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/IDGenerator.h" +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +namespace Swift { + class TLSLayerFactory; + class ConnectionFactory; + class Session; + + class Client : public StanzaChannel, public IQRouter { + public: + Client(const JID& jid, const String& password); + ~Client(); + + void setCertificate(const String& certificate); + + void connect(); + void disconnect(); + + virtual void sendIQ(boost::shared_ptr<IQ>); + virtual void sendMessage(boost::shared_ptr<Message>); + virtual void sendPresence(boost::shared_ptr<Presence>); + + public: + boost::signal<void (ClientError)> onError; + boost::signal<void ()> onConnected; + boost::signal<void (const String&)> onDataRead; + boost::signal<void (const String&)> onDataWritten; + + private: + void send(boost::shared_ptr<Stanza>); + virtual String getNewIQID(); + void handleElement(boost::shared_ptr<Element>); + void handleSessionError(Session::SessionError error); + void handleNeedCredentials(); + void handleDataRead(const ByteArray&); + void handleDataWritten(const ByteArray&); + + private: + JID jid_; + String password_; + IDGenerator idGenerator_; + ConnectionFactory* connectionFactory_; + TLSLayerFactory* tlsLayerFactory_; + FullPayloadParserFactoryCollection payloadParserFactories_; + FullPayloadSerializerCollection payloadSerializers_; + Session* session_; + String certificate_; + }; +} + +#endif diff --git a/Swiften/Client/ClientError.h b/Swiften/Client/ClientError.h new file mode 100644 index 0000000..38f20c0 --- /dev/null +++ b/Swiften/Client/ClientError.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_ClientError_H +#define SWIFTEN_ClientError_H + +namespace Swift { + class ClientError { + public: + enum Type { + NoError, + DomainNameResolveError, + ConnectionError, + ConnectionReadError, + XMLError, + AuthenticationFailedError, + NoSupportedAuthMechanismsError, + UnexpectedElementError, + ResourceBindError, + SessionStartError, + TLSError, + ClientCertificateLoadError, + ClientCertificateError + }; + + ClientError(Type type = NoError) : type_(type) {} + + Type getType() const { return type_; } + + private: + Type type_; + }; +} + +#endif diff --git a/Swiften/Client/Makefile.inc b/Swiften/Client/Makefile.inc new file mode 100644 index 0000000..75eb08f --- /dev/null +++ b/Swiften/Client/Makefile.inc @@ -0,0 +1,5 @@ +SWIFTEN_SOURCES += \ + Swiften/Client/Client.cpp \ + Swiften/Client/Session.cpp + +include Swiften/Client/UnitTest/Makefile.inc diff --git a/Swiften/Client/Session.cpp b/Swiften/Client/Session.cpp new file mode 100644 index 0000000..aa3cc62 --- /dev/null +++ b/Swiften/Client/Session.cpp @@ -0,0 +1,292 @@ +#include "Swiften/Client/Session.h" + +#include <boost/bind.hpp> + +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/ConnectionLayer.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/TLSLayer.h" +#include "Swiften/StreamStack/TLSLayerFactory.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/SASL/PLAINMessage.h" +#include "Swiften/StreamStack/WhitespacePingLayer.h" + +namespace Swift { + +Session::Session(const JID& jid, ConnectionFactory* connectionFactory, TLSLayerFactory* tlsLayerFactory, PayloadParserFactoryCollection* payloadParserFactories, PayloadSerializerCollection* payloadSerializers) : + jid_(jid), + connectionFactory_(connectionFactory), + tlsLayerFactory_(tlsLayerFactory), + payloadParserFactories_(payloadParserFactories), + payloadSerializers_(payloadSerializers), + state_(Initial), + error_(NoError), + connection_(0), + xmppLayer_(0), + tlsLayer_(0), + connectionLayer_(0), + whitespacePingLayer_(0), + streamStack_(0), + needSessionStart_(false) { +} + +Session::~Session() { + delete streamStack_; + delete whitespacePingLayer_; + delete connectionLayer_; + delete tlsLayer_; + delete xmppLayer_; + delete connection_; +} + +void Session::start() { + assert(state_ == Initial); + state_ = Connecting; + connection_ = connectionFactory_->createConnection(jid_.getDomain()); + connection_->onConnected.connect(boost::bind(&Session::handleConnected, this)); + connection_->onError.connect(boost::bind(&Session::handleConnectionError, this, _1)); + connection_->connect(); +} + +void Session::stop() { + // TODO: Send end stream header if applicable + connection_->disconnect(); +} + +void Session::handleConnected() { + assert(state_ == Connecting); + initializeStreamStack(); + state_ = WaitingForStreamStart; + sendStreamHeader(); +} + +void Session::sendStreamHeader() { + xmppLayer_->writeHeader(jid_.getDomain()); +} + +void Session::initializeStreamStack() { + xmppLayer_ = new XMPPLayer(payloadParserFactories_, payloadSerializers_); + xmppLayer_->onStreamStart.connect(boost::bind(&Session::handleStreamStart, this)); + xmppLayer_->onElement.connect(boost::bind(&Session::handleElement, this, _1)); + xmppLayer_->onError.connect(boost::bind(&Session::setError, this, XMLError)); + xmppLayer_->onDataRead.connect(boost::bind(boost::ref(onDataRead), _1)); + xmppLayer_->onWriteData.connect(boost::bind(boost::ref(onDataWritten), _1)); + connectionLayer_ = new ConnectionLayer(connection_); + streamStack_ = new StreamStack(xmppLayer_, connectionLayer_); +} + +void Session::handleConnectionError(Connection::Error error) { + switch (error) { + case Connection::DomainNameResolveError: + setError(DomainNameResolveError); + break; + case Connection::ReadError: + setError(ConnectionReadError); + break; + case Connection::ConnectionError: + setError(ConnectionError); + break; + } +} + +void Session::setCertificate(const PKCS12Certificate& certificate) { + certificate_ = certificate; +} + +void Session::handleStreamStart() { + checkState(WaitingForStreamStart); + state_ = Negotiating; +} + +void Session::handleElement(boost::shared_ptr<Element> element) { + if (getState() == SessionStarted) { + onElementReceived(element); + } + else { + StreamFeatures* streamFeatures = dynamic_cast<StreamFeatures*>(element.get()); + if (streamFeatures) { + if (!checkState(Negotiating)) { + return; + } + + if (streamFeatures->hasStartTLS() && tlsLayerFactory_->canCreate()) { + state_ = Encrypting; + xmppLayer_->writeElement(boost::shared_ptr<StartTLSRequest>(new StartTLSRequest())); + } + else if (streamFeatures->hasAuthenticationMechanisms()) { + if (!certificate_.isNull()) { + if (streamFeatures->hasAuthenticationMechanism("EXTERNAL")) { + state_ = Authenticating; + xmppLayer_->writeElement(boost::shared_ptr<Element>(new AuthRequest("EXTERNAL", ""))); + } + else { + setError(ClientCertificateError); + } + } + else if (streamFeatures->hasAuthenticationMechanism("PLAIN")) { + state_ = WaitingForCredentials; + onNeedCredentials(); + } + else { + setError(NoSupportedAuthMechanismsError); + } + } + else { + // Start the session + + // Add a whitespace ping layer + whitespacePingLayer_ = new WhitespacePingLayer(); + streamStack_->addLayer(whitespacePingLayer_); + + if (streamFeatures->hasSession()) { + needSessionStart_ = true; + } + + if (streamFeatures->hasResourceBind()) { + state_ = BindingResource; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + if (!jid_.getResource().isEmpty()) { + resourceBind->setResource(jid_.getResource()); + } + xmppLayer_->writeElement(IQ::createRequest(IQ::Set, JID(), "session-bind", resourceBind)); + } + else if (needSessionStart_) { + sendSessionStart(); + } + else { + state_ = SessionStarted; + onSessionStarted(); + } + } + } + else { + AuthSuccess* authSuccess = dynamic_cast<AuthSuccess*>(element.get()); + if (authSuccess) { + checkState(Authenticating); + state_ = WaitingForStreamStart; + xmppLayer_->resetParser(); + sendStreamHeader(); + } + else if (dynamic_cast<AuthFailure*>(element.get())) { + setError(AuthenticationFailedError); + } + else if (dynamic_cast<TLSProceed*>(element.get())) { + tlsLayer_ = tlsLayerFactory_->createTLSLayer(); + streamStack_->addLayer(tlsLayer_); + if (!certificate_.isNull() && !tlsLayer_->setClientCertificate(certificate_)) { + setError(ClientCertificateLoadError); + } + else { + tlsLayer_->onConnected.connect(boost::bind(&Session::handleTLSConnected, this)); + tlsLayer_->onError.connect(boost::bind(&Session::handleTLSError, this)); + tlsLayer_->connect(); + } + } + else if (dynamic_cast<StartTLSFailure*>(element.get())) { + setError(TLSError); + } + else { + IQ* iq = dynamic_cast<IQ*>(element.get()); + if (iq) { + if (state_ == BindingResource) { + boost::shared_ptr<ResourceBind> resourceBind(iq->getPayload<ResourceBind>()); + if (iq->getType() == IQ::Error && iq->getID() == "session-bind") { + setError(ResourceBindError); + } + else if (!resourceBind) { + setError(UnexpectedElementError); + } + else if (iq->getType() == IQ::Result) { + jid_ = resourceBind->getJID(); + if (!jid_.isValid()) { + setError(ResourceBindError); + } + if (needSessionStart_) { + sendSessionStart(); + } + else { + state_ = SessionStarted; + } + } + else { + setError(UnexpectedElementError); + } + } + else if (state_ == StartingSession) { + if (iq->getType() == IQ::Result) { + state_ = SessionStarted; + onSessionStarted(); + } + else if (iq->getType() == IQ::Error) { + setError(SessionStartError); + } + else { + setError(UnexpectedElementError); + } + } + else { + setError(UnexpectedElementError); + } + } + else { + // FIXME Not correct? + state_ = SessionStarted; + onSessionStarted(); + } + } + } + } +} + +void Session::sendSessionStart() { + state_ = StartingSession; + xmppLayer_->writeElement(IQ::createRequest(IQ::Set, JID(), "session-start", boost::shared_ptr<StartSession>(new StartSession()))); +} + +void Session::setError(SessionError error) { + assert(error != NoError); + state_ = Error; + error_ = error; + onError(error); +} + +bool Session::checkState(State state) { + if (state_ != state) { + setError(UnexpectedElementError); + return false; + } + return true; +} + +void Session::sendCredentials(const String& password) { + assert(WaitingForCredentials); + state_ = Authenticating; + xmppLayer_->writeElement(boost::shared_ptr<Element>(new AuthRequest("PLAIN", PLAINMessage(jid_.getNode(), password).getValue()))); +} + +void Session::sendElement(boost::shared_ptr<Element> element) { + assert(SessionStarted); + xmppLayer_->writeElement(element); +} + +void Session::handleTLSConnected() { + state_ = WaitingForStreamStart; + xmppLayer_->resetParser(); + sendStreamHeader(); +} + +void Session::handleTLSError() { + setError(TLSError); +} + +} diff --git a/Swiften/Client/Session.h b/Swiften/Client/Session.h new file mode 100644 index 0000000..c49d877 --- /dev/null +++ b/Swiften/Client/Session.h @@ -0,0 +1,126 @@ +#ifndef SWIFTEN_Session_H +#define SWIFTEN_Session_H + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +namespace Swift { + class PayloadParserFactoryCollection; + class PayloadSerializerCollection; + class ConnectionFactory; + class Connection; + class StreamStack; + class XMPPLayer; + class ConnectionLayer; + class TLSLayerFactory; + class TLSLayer; + class WhitespacePingLayer; + + class Session { + public: + enum State { + Initial, + Connecting, + WaitingForStreamStart, + Negotiating, + Compressing, + Encrypting, + WaitingForCredentials, + Authenticating, + BindingResource, + StartingSession, + SessionStarted, + Error + }; + enum SessionError { + NoError, + DomainNameResolveError, + ConnectionError, + ConnectionReadError, + XMLError, + AuthenticationFailedError, + NoSupportedAuthMechanismsError, + UnexpectedElementError, + ResourceBindError, + SessionStartError, + TLSError, + ClientCertificateLoadError, + ClientCertificateError + }; + + Session(const JID& jid, ConnectionFactory*, TLSLayerFactory*, PayloadParserFactoryCollection*, PayloadSerializerCollection*); + ~Session(); + + State getState() const { + return state_; + } + + SessionError getError() const { + return error_; + } + + const JID& getJID() const { + return jid_; + } + + void start(); + void stop(); + void sendCredentials(const String& password); + void sendElement(boost::shared_ptr<Element>); + void setCertificate(const PKCS12Certificate& certificate); + + protected: + StreamStack* getStreamStack() const { + return streamStack_; + } + + private: + void initializeStreamStack(); + void sendStreamHeader(); + void sendSessionStart(); + + void handleConnected(); + void handleConnectionError(Connection::Error); + void handleElement(boost::shared_ptr<Element>); + void handleStreamStart(); + void handleTLSConnected(); + void handleTLSError(); + + void setError(SessionError); + bool checkState(State); + + public: + boost::signal<void ()> onSessionStarted; + boost::signal<void (SessionError)> onError; + boost::signal<void ()> onNeedCredentials; + boost::signal<void (boost::shared_ptr<Element>) > onElementReceived; + boost::signal<void (const ByteArray&)> onDataWritten; + boost::signal<void (const ByteArray&)> onDataRead; + + private: + JID jid_; + ConnectionFactory* connectionFactory_; + TLSLayerFactory* tlsLayerFactory_; + PayloadParserFactoryCollection* payloadParserFactories_; + PayloadSerializerCollection* payloadSerializers_; + State state_; + SessionError error_; + Connection* connection_; + XMPPLayer* xmppLayer_; + TLSLayer* tlsLayer_; + ConnectionLayer* connectionLayer_; + WhitespacePingLayer* whitespacePingLayer_; + StreamStack* streamStack_; + bool needSessionStart_; + PKCS12Certificate certificate_; + }; + +} + +#endif diff --git a/Swiften/Client/StanzaChannel.h b/Swiften/Client/StanzaChannel.h new file mode 100644 index 0000000..719ed10 --- /dev/null +++ b/Swiften/Client/StanzaChannel.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_MessageChannel_H +#define SWIFTEN_MessageChannel_H + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Queries/IQChannel.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class StanzaChannel : public IQChannel { + public: + virtual void sendMessage(boost::shared_ptr<Message>) = 0; + virtual void sendPresence(boost::shared_ptr<Presence>) = 0; + + boost::signal<void (boost::shared_ptr<Message>)> onMessageReceived; + boost::signal<void (boost::shared_ptr<Presence>) > onPresenceReceived; + }; +} + +#endif diff --git a/Swiften/Client/UnitTest/Makefile.inc b/Swiften/Client/UnitTest/Makefile.inc new file mode 100644 index 0000000..3ef87e5 --- /dev/null +++ b/Swiften/Client/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Client/UnitTest/SessionTest.cpp diff --git a/Swiften/Client/UnitTest/SessionTest.cpp b/Swiften/Client/UnitTest/SessionTest.cpp new file mode 100644 index 0000000..7b7a916 --- /dev/null +++ b/Swiften/Client/UnitTest/SessionTest.cpp @@ -0,0 +1,752 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Parser/XMPPParserClient.h" +#include "Swiften/Serializer/XMPPSerializer.h" +#include "Swiften/StreamStack/TLSLayerFactory.h" +#include "Swiften/StreamStack/TLSLayer.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/WhitespacePingLayer.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Elements/Error.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/EventLoop/DummyEventLoop.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Client/Session.h" +#include "Swiften/TLS/PKCS12Certificate.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +using namespace Swift; + +class SessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SessionTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testConnect); + CPPUNIT_TEST(testConnect_Error); + CPPUNIT_TEST(testConnect_ErrorAfterSuccesfulConnect); + CPPUNIT_TEST(testConnect_XMLError); + CPPUNIT_TEST(testStartTLS); + CPPUNIT_TEST(testStartTLS_ServerError); + CPPUNIT_TEST(testStartTLS_NoTLSSupport); + CPPUNIT_TEST(testStartTLS_ConnectError); + CPPUNIT_TEST(testStartTLS_ErrorAfterConnect); + CPPUNIT_TEST(testAuthenticate); + CPPUNIT_TEST(testAuthenticate_Unauthorized); + CPPUNIT_TEST(testAuthenticate_NoValidAuthMechanisms); + CPPUNIT_TEST(testResourceBind); + CPPUNIT_TEST(testResourceBind_ChangeResource); + CPPUNIT_TEST(testResourceBind_EmptyResource); + CPPUNIT_TEST(testResourceBind_Error); + CPPUNIT_TEST(testSessionStart); + CPPUNIT_TEST(testSessionStart_Error); + CPPUNIT_TEST(testSessionStart_AfterResourceBind); + CPPUNIT_TEST(testWhitespacePing); + CPPUNIT_TEST(testReceiveElementAfterSessionStarted); + CPPUNIT_TEST(testSendElement); + CPPUNIT_TEST_SUITE_END(); + + public: + SessionTest() {} + + void setUp() { + eventLoop_ = new DummyEventLoop(); + connectionFactory_ = new MockConnectionFactory(); + tlsLayerFactory_ = new MockTLSLayerFactory(); + sessionStarted_ = false; + needCredentials_ = false; + } + + void tearDown() { + delete tlsLayerFactory_; + delete connectionFactory_; + delete eventLoop_; + } + + void testConstructor() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + CPPUNIT_ASSERT_EQUAL(Session::Initial, session->getState()); + } + + void testConnect() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + + session->start(); + CPPUNIT_ASSERT_EQUAL(Session::Connecting, session->getState()); + + getMockServer()->expectStreamStart(); + + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::WaitingForStreamStart, session->getState()); + } + + void testConnect_Error() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onSessionStarted.connect(boost::bind(&SessionTest::setSessionStarted, this)); + + connectionFactory_->setCreateFailingConnections(); + session->start(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT(!sessionStarted_); + CPPUNIT_ASSERT_EQUAL(Session::ConnectionError, session->getError()); + } + + void testConnect_ErrorAfterSuccesfulConnect() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + + session->start(); + getMockServer()->expectStreamStart(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::WaitingForStreamStart, session->getState()); + + connectionFactory_->connections_[0]->setError(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::ConnectionError, session->getError()); + } + + void testConnect_XMLError() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + + session->start(); + getMockServer()->expectStreamStart(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::WaitingForStreamStart, session->getState()); + + getMockServer()->sendInvalidXML(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::XMLError, session->getError()); + } + + void testStartTLS_NoTLSSupport() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + tlsLayerFactory_->setTLSSupported(false); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithStartTLS(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + } + + void testStartTLS() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithStartTLS(); + getMockServer()->expectStartTLS(); + // FIXME: Test 'encrypting' state + getMockServer()->sendTLSProceed(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::Encrypting, session->getState()); + CPPUNIT_ASSERT(session->getTLSLayer()); + CPPUNIT_ASSERT(session->getTLSLayer()->isConnecting()); + + getMockServer()->resetParser(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + session->getTLSLayer()->setConnected(); + // FIXME: Test 'WatingForStreamStart' state + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::Negotiating, session->getState()); + } + + void testStartTLS_ServerError() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithStartTLS(); + getMockServer()->expectStartTLS(); + getMockServer()->sendTLSFailure(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::TLSError, session->getError()); + } + + void testStartTLS_ConnectError() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithStartTLS(); + getMockServer()->expectStartTLS(); + getMockServer()->sendTLSProceed(); + processEvents(); + session->getTLSLayer()->setError(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::TLSError, session->getError()); + } + + void testStartTLS_ErrorAfterConnect() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithStartTLS(); + getMockServer()->expectStartTLS(); + getMockServer()->sendTLSProceed(); + processEvents(); + getMockServer()->resetParser(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + session->getTLSLayer()->setConnected(); + processEvents(); + + session->getTLSLayer()->setError(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::TLSError, session->getError()); + } + + void testAuthenticate() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onNeedCredentials.connect(boost::bind(&SessionTest::setNeedCredentials, this)); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithAuthentication(); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::WaitingForCredentials, session->getState()); + CPPUNIT_ASSERT(needCredentials_); + + getMockServer()->expectAuth("me", "mypass"); + getMockServer()->sendAuthSuccess(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + session->sendCredentials("mypass"); + CPPUNIT_ASSERT_EQUAL(Session::Authenticating, session->getState()); + processEvents(); + CPPUNIT_ASSERT_EQUAL(Session::Negotiating, session->getState()); + } + + void testAuthenticate_Unauthorized() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithAuthentication(); + processEvents(); + + getMockServer()->expectAuth("me", "mypass"); + getMockServer()->sendAuthFailure(); + session->sendCredentials("mypass"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::AuthenticationFailedError, session->getError()); + } + + void testAuthenticate_NoValidAuthMechanisms() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithUnsupportedAuthentication(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::NoSupportedAuthMechanismsError, session->getError()); + } + + void testResourceBind() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + // FIXME: Check CPPUNIT_ASSERT_EQUAL(Session::BindingResource, session->getState()); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar"), session->getJID()); + } + + void testResourceBind_ChangeResource() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar123", "session-bind"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar123"), session->getJID()); + } + + void testResourceBind_EmptyResource() { + std::auto_ptr<MockSession> session(createSession("me@foo.com")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/NewResource", "session-bind"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/NewResource"), session->getJID()); + } + + void testResourceBind_Error() { + std::auto_ptr<MockSession> session(createSession("me@foo.com")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBind(); + getMockServer()->expectResourceBind("", "session-bind"); + getMockServer()->sendError("session-bind"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::ResourceBindError, session->getError()); + } + + void testSessionStart() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onSessionStarted.connect(boost::bind(&SessionTest::setSessionStarted, this)); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithSession(); + getMockServer()->expectSessionStart("session-start"); + // FIXME: Check CPPUNIT_ASSERT_EQUAL(Session::StartingSession, session->getState()); + getMockServer()->sendSessionStartResponse("session-start"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + CPPUNIT_ASSERT(sessionStarted_); + } + + void testSessionStart_Error() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithSession(); + getMockServer()->expectSessionStart("session-start"); + getMockServer()->sendError("session-start"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::Error, session->getState()); + CPPUNIT_ASSERT_EQUAL(Session::SessionStartError, session->getError()); + } + + void testSessionStart_AfterResourceBind() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onSessionStarted.connect(boost::bind(&SessionTest::setSessionStarted, this)); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeaturesWithResourceBindAndSession(); + getMockServer()->expectResourceBind("Bar", "session-bind"); + getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind"); + getMockServer()->expectSessionStart("session-start"); + getMockServer()->sendSessionStartResponse("session-start"); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(Session::SessionStarted, session->getState()); + CPPUNIT_ASSERT(sessionStarted_); + } + + void testWhitespacePing() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + processEvents(); + CPPUNIT_ASSERT(session->getWhitespacePingLayer()); + } + + void testReceiveElementAfterSessionStarted() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + processEvents(); + + getMockServer()->expectMessage(); + session->sendElement(boost::shared_ptr<Message>(new Message())); + } + + void testSendElement() { + std::auto_ptr<MockSession> session(createSession("me@foo.com/Bar")); + session->onElementReceived.connect(boost::bind(&SessionTest::addReceivedElement, this, _1)); + session->start(); + getMockServer()->expectStreamStart(); + getMockServer()->sendStreamStart(); + getMockServer()->sendStreamFeatures(); + getMockServer()->sendMessage(); + processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(receivedElements_.size())); + CPPUNIT_ASSERT(boost::dynamic_pointer_cast<Message>(receivedElements_[0])); + } + + private: + struct MockConnection; + + MockConnection* getMockServer() const { + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connectionFactory_->connections_.size())); + return connectionFactory_->connections_[0]; + } + + void processEvents() { + eventLoop_->processEvents(); + getMockServer()->assertNoMoreExpectations(); + } + + void setSessionStarted() { + sessionStarted_ = true; + } + + void setNeedCredentials() { + needCredentials_ = true; + } + + void addReceivedElement(boost::shared_ptr<Element> element) { + receivedElements_.push_back(element); + } + + private: + struct MockConnection : public Connection, public XMPPParserClient { + struct Event { + enum Direction { In, Out }; + enum Type { StreamStartEvent, StreamEndEvent, ElementEvent }; + + Event( + Direction direction, + Type type, + boost::shared_ptr<Element> element = boost::shared_ptr<Element>()) : + direction(direction), type(type), element(element) {} + + Direction direction; + Type type; + boost::shared_ptr<Element> element; + }; + + MockConnection(const String& domain, bool fail) : + Connection(domain), + fail_(fail), + resetParser_(false), + parser_(0), + serializer_(&payloadSerializers_) { + parser_ = new XMPPParser(this, &payloadParserFactories_); + } + + ~MockConnection() { + delete parser_; + } + + void disconnect() { + } + + void connect() { + if (fail_) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), Connection::ConnectionError)); + } + else { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnected))); + } + } + + void setError() { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), Connection::ConnectionError)); + } + + void write(const ByteArray& data) { + CPPUNIT_ASSERT(parser_->parse(data.toString())); + if (resetParser_) { + resetParser(); + resetParser_ = false; + } + } + + void resetParser() { + delete parser_; + parser_ = new XMPPParser(this, &payloadParserFactories_); + } + + void handleStreamStart() { + handleEvent(Event::StreamStartEvent); + } + + void handleElement(boost::shared_ptr<Swift::Element> element) { + handleEvent(Event::ElementEvent, element); + } + + void handleStreamEnd() { + handleEvent(Event::StreamEndEvent); + } + + void handleEvent(Event::Type type, boost::shared_ptr<Element> element = boost::shared_ptr<Element>()) { + CPPUNIT_ASSERT(!events_.empty()); + CPPUNIT_ASSERT_EQUAL(events_[0].direction, Event::In); + CPPUNIT_ASSERT_EQUAL(events_[0].type, type); + if (type == Event::ElementEvent) { + CPPUNIT_ASSERT_EQUAL(serializer_.serializeElement(events_[0].element), serializer_.serializeElement(element)); + } + events_.pop_front(); + + while (!events_.empty() && events_[0].direction == Event::Out) { + sendData(serializeEvent(events_[0])); + events_.pop_front(); + } + + if (!events_.empty() && events_[0].type == Event::StreamStartEvent) { + resetParser_ = true; + } + } + + String serializeEvent(const Event& event) { + switch (event.type) { + case Event::StreamStartEvent: + return serializer_.serializeHeader(getDomain()); + case Event::ElementEvent: + return serializer_.serializeElement(event.element); + case Event::StreamEndEvent: + return serializer_.serializeFooter(); + } + assert(false); + } + + void assertNoMoreExpectations() { + CPPUNIT_ASSERT(events_.empty()); + } + + void sendData(const ByteArray& data) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDataRead), data)); + } + + void expectStreamStart() { + events_.push_back(Event(Event::In, Event::StreamStartEvent)); + } + + void expectStartTLS() { + events_.push_back(Event(Event::In, Event::ElementEvent, boost::shared_ptr<StartTLSRequest>(new StartTLSRequest()))); + } + + void expectAuth(const String& user, const String& password) { + String s = String("") + '\0' + user + '\0' + password; + events_.push_back(Event(Event::In, Event::ElementEvent, boost::shared_ptr<AuthRequest>(new AuthRequest("PLAIN", ByteArray(s.getUTF8Data(), s.getUTF8Size()))))); + } + + void expectResourceBind(const String& resource, const String& id) { + boost::shared_ptr<ResourceBind> sessionStart(new ResourceBind()); + sessionStart->setResource(resource); + events_.push_back(Event(Event::In, Event::ElementEvent, IQ::createRequest(IQ::Set, JID(), id, sessionStart))); + } + + void expectSessionStart(const String& id) { + events_.push_back(Event(Event::In, Event::ElementEvent, IQ::createRequest(IQ::Set, JID(), id, boost::shared_ptr<StartSession>(new StartSession())))); + } + + void expectMessage() { + events_.push_back(Event(Event::In, Event::ElementEvent, boost::shared_ptr<Message>(new Message()))); + } + + void sendInvalidXML() { + sendData("<invalid xml/>"); + } + + void sendStreamStart() { + events_.push_back(Event(Event::Out, Event::StreamStartEvent)); + } + + void sendStreamFeatures() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithStartTLS() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasStartTLS(); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithAuthentication() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->addAuthenticationMechanism("PLAIN"); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithUnsupportedAuthentication() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->addAuthenticationMechanism("MY-UNSUPPORTED-MECH"); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithResourceBind() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasResourceBind(); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithSession() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasSession(); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendStreamFeaturesWithResourceBindAndSession() { + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasResourceBind(); + streamFeatures->setHasSession(); + events_.push_back(Event(Event::Out, Event::ElementEvent, streamFeatures)); + } + + void sendMessage() { + events_.push_back(Event(Event::Out, Event::ElementEvent, boost::shared_ptr<Message>(new Message()))); + } + + void sendTLSProceed() { + events_.push_back(Event(Event::Out, Event::ElementEvent, boost::shared_ptr<TLSProceed>(new TLSProceed()))); + } + + void sendTLSFailure() { + events_.push_back(Event(Event::Out, Event::ElementEvent, boost::shared_ptr<StartTLSFailure>(new StartTLSFailure()))); + } + + void sendAuthSuccess() { + events_.push_back(Event(Event::Out, Event::ElementEvent, boost::shared_ptr<AuthSuccess>(new AuthSuccess()))); + } + + void sendAuthFailure() { + events_.push_back(Event(Event::Out, Event::ElementEvent, boost::shared_ptr<AuthFailure>(new AuthFailure()))); + } + + void sendResourceBindResponse(const String& jid, const String& id) { + boost::shared_ptr<ResourceBind> sessionStart(new ResourceBind()); + sessionStart->setJID(JID(jid)); + events_.push_back(Event(Event::Out, Event::ElementEvent, IQ::createResult(JID(), id, sessionStart))); + } + + void sendError(const String& id) { + events_.push_back(Event(Event::Out, Event::ElementEvent, IQ::createError(JID(), id, Swift::Error::NotAllowed, Swift::Error::Cancel))); + } + + void sendSessionStartResponse(const String& id) { + events_.push_back(Event(Event::Out, Event::ElementEvent, IQ::createResult(JID(), id, boost::shared_ptr<StartSession>(new StartSession())))); + } + + bool fail_; + bool resetParser_; + FullPayloadParserFactoryCollection payloadParserFactories_; + FullPayloadSerializerCollection payloadSerializers_; + XMPPParser* parser_; + XMPPSerializer serializer_; + std::deque<Event> events_; + }; + + struct MockConnectionFactory : public ConnectionFactory { + MockConnectionFactory() : fail_(false) {} + MockConnection* createConnection(const String& domain) { + MockConnection* result = new MockConnection(domain, fail_); + connections_.push_back(result); + return result; + } + void setCreateFailingConnections() { + fail_ = true; + } + std::vector<MockConnection*> connections_; + bool fail_; + }; + + struct MockTLSLayer : public TLSLayer { + MockTLSLayer() : connecting_(false) {} + bool setClientCertificate(const PKCS12Certificate&) { return true; } + void writeData(const ByteArray& data) { onWriteData(data); } + void handleDataRead(const ByteArray& data) { onDataRead(data); } + void setConnected() { onConnected(); } + void setError() { onError(); } + void connect() { connecting_ = true; } + bool isConnecting() { return connecting_; } + + bool connecting_; + }; + + struct MockTLSLayerFactory : public TLSLayerFactory { + MockTLSLayerFactory() : haveTLS_(true) {} + void setTLSSupported(bool b) { haveTLS_ = b; } + virtual bool canCreate() const { return haveTLS_; } + virtual TLSLayer* createTLSLayer() { + assert(haveTLS_); + MockTLSLayer* result = new MockTLSLayer(); + layers_.push_back(result); + return result; + } + std::vector<MockTLSLayer*> layers_; + bool haveTLS_; + }; + + struct MockSession : public Session { + MockSession(const JID& jid, ConnectionFactory* connectionFactory, TLSLayerFactory* tlsLayerFactory, PayloadParserFactoryCollection* payloadParserFactories, PayloadSerializerCollection* payloadSerializers) : Session(jid, connectionFactory, tlsLayerFactory, payloadParserFactories, payloadSerializers) {} + + MockTLSLayer* getTLSLayer() const { + return getStreamStack()->getLayer<MockTLSLayer>(); + } + WhitespacePingLayer* getWhitespacePingLayer() const { + return getStreamStack()->getLayer<WhitespacePingLayer>(); + } + }; + + MockSession* createSession(const String& jid) { + return new MockSession(JID(jid), connectionFactory_, tlsLayerFactory_, &payloadParserFactories_, &payloadSerializers_); + } + + + DummyEventLoop* eventLoop_; + MockConnectionFactory* connectionFactory_; + MockTLSLayerFactory* tlsLayerFactory_; + FullPayloadParserFactoryCollection payloadParserFactories_; + FullPayloadSerializerCollection payloadSerializers_; + bool sessionStarted_; + bool needCredentials_; + std::vector< boost::shared_ptr<Element> > receivedElements_; + typedef std::vector< boost::function<void ()> > EventQueue; + EventQueue events_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SessionTest); diff --git a/Swiften/Compress/Makefile.inc b/Swiften/Compress/Makefile.inc new file mode 100644 index 0000000..bf502fb --- /dev/null +++ b/Swiften/Compress/Makefile.inc @@ -0,0 +1,4 @@ +SWIFTEN_SOURCES += \ + Swiften/Compress/ZLibCodecompressor.cpp + +include Swiften/Compress/UnitTest/Makefile.inc diff --git a/Swiften/Compress/UnitTest/Makefile.inc b/Swiften/Compress/UnitTest/Makefile.inc new file mode 100644 index 0000000..fcc1a2c --- /dev/null +++ b/Swiften/Compress/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp \ + Swiften/Compress/UnitTest/ZLibCompressorTest.cpp diff --git a/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp b/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp new file mode 100644 index 0000000..7235f8e --- /dev/null +++ b/Swiften/Compress/UnitTest/ZLibCompressorTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Compress/ZLibCompressor.h" + +using namespace Swift; + + +class ZLibCompressorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ZLibCompressorTest); + CPPUNIT_TEST(testProcess); + CPPUNIT_TEST(testProcess_Twice); + CPPUNIT_TEST_SUITE_END(); + + public: + ZLibCompressorTest() {} + + void testProcess() { + ZLibCompressor testling; + ByteArray result = testling.process("foo"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11), result); + } + + void testProcess_Twice() { + ZLibCompressor testling; + testling.process("foo"); + ByteArray result = testling.process("bar"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x4a\x4a\x2c\x02\x00\x00\x00\xff\xff",9), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ZLibCompressorTest); diff --git a/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp b/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp new file mode 100644 index 0000000..871a630 --- /dev/null +++ b/Swiften/Compress/UnitTest/ZLibDecompressorTest.cpp @@ -0,0 +1,71 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Compress/ZLibDecompressor.h" +#include "Swiften/Compress/ZLibCompressor.h" +#include "Swiften/Compress/ZLibException.h" + +using namespace Swift; + + +class ZLibDecompressorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ZLibDecompressorTest); + CPPUNIT_TEST(testProcess); + CPPUNIT_TEST(testProcess_Twice); + CPPUNIT_TEST(testProcess_Invalid); + CPPUNIT_TEST(testProcess_Huge); + CPPUNIT_TEST(testProcess_ChunkSize); + CPPUNIT_TEST_SUITE_END(); + + public: + ZLibDecompressorTest() {} + + void testProcess() { + ZLibDecompressor testling; + ByteArray result = testling.process(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("foo"), result); + } + + void testProcess_Twice() { + ZLibDecompressor testling; + testling.process(ByteArray("\x78\xda\x4a\xcb\xcf\x07\x00\x00\x00\xff\xff", 11)); + ByteArray result = testling.process(ByteArray("\x4a\x4a\x2c\x02\x00\x00\x00\xff\xff", 9)); + + CPPUNIT_ASSERT_EQUAL(ByteArray("bar"), result); + } + + void testProcess_Invalid() { + ZLibDecompressor testling; + CPPUNIT_ASSERT_THROW(testling.process(ByteArray("invalid")), ZLibException); + } + + void testProcess_Huge() { + std::vector<char> data; + data.reserve(2048); + for (unsigned int i = 0; i < 2048; ++i) { + data.push_back(static_cast<char>(i)); + } + ByteArray original(&data[0], data.size()); + ByteArray compressed = ZLibCompressor().process(original); + ByteArray decompressed = ZLibDecompressor().process(compressed); + + CPPUNIT_ASSERT_EQUAL(original, decompressed); + } + + void testProcess_ChunkSize() { + std::vector<char> data; + data.reserve(1024); + for (unsigned int i = 0; i < 1024; ++i) { + data.push_back(static_cast<char>(i)); + } + ByteArray original(&data[0], data.size()); + ByteArray compressed = ZLibCompressor().process(original); + ByteArray decompressed = ZLibDecompressor().process(compressed); + + CPPUNIT_ASSERT_EQUAL(original, decompressed); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ZLibDecompressorTest); diff --git a/Swiften/Compress/ZLibCodecompressor.cpp b/Swiften/Compress/ZLibCodecompressor.cpp new file mode 100644 index 0000000..a14f09d --- /dev/null +++ b/Swiften/Compress/ZLibCodecompressor.cpp @@ -0,0 +1,43 @@ +#include "Swiften/Compress/ZLibCodecompressor.h" + +#include <cassert> + +#include "Swiften/Compress/ZLibException.h" + +namespace Swift { + +static const int CHUNK_SIZE = 1024; // If you change this, also change the unittest + +ZLibCodecompressor::ZLibCodecompressor() { + stream_.zalloc = Z_NULL; + stream_.zfree = Z_NULL; + stream_.opaque = Z_NULL; +} + +ZLibCodecompressor::~ZLibCodecompressor() { +} + +ByteArray ZLibCodecompressor::process(const ByteArray& input) { + ByteArray output; + stream_.avail_in = input.getSize(); + stream_.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(input.getData())); + int outputPosition = 0; + do { + output.resize(outputPosition + CHUNK_SIZE); + stream_.avail_out = CHUNK_SIZE; + stream_.next_out = reinterpret_cast<Bytef*>(output.getData() + outputPosition); + int result = processZStream(); + if (result != Z_OK && result != Z_BUF_ERROR) { + throw ZLibException(/* stream_.msg */); + } + outputPosition += CHUNK_SIZE; + } + while (stream_.avail_out == 0); + if (stream_.avail_in != 0) { + throw ZLibException(); + } + output.resize(outputPosition - stream_.avail_out); + return output; +} + +} diff --git a/Swiften/Compress/ZLibCodecompressor.h b/Swiften/Compress/ZLibCodecompressor.h new file mode 100644 index 0000000..dd032fa --- /dev/null +++ b/Swiften/Compress/ZLibCodecompressor.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_ZLibCodecompressor_H +#define SWIFTEN_ZLibCodecompressor_H + +#include <zlib.h> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibCodecompressor { + public: + ZLibCodecompressor(); + virtual ~ZLibCodecompressor(); + + ByteArray process(const ByteArray& data); + virtual int processZStream() = 0; + + protected: + z_stream stream_; + }; +} + +#endif diff --git a/Swiften/Compress/ZLibCompressor.h b/Swiften/Compress/ZLibCompressor.h new file mode 100644 index 0000000..b5bace6 --- /dev/null +++ b/Swiften/Compress/ZLibCompressor.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_ZLibCompressor_H +#define SWIFTEN_ZLibCompressor_H + +#include <cassert> + +#include "Swiften/Compress/ZLibCodecompressor.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibCompressor : public ZLibCodecompressor { + public: + ZLibCompressor() { + int result = deflateInit(&stream_, COMPRESSION_LEVEL); + assert(result == Z_OK); + (void) result; + } + + ~ZLibCompressor() { + deflateEnd(&stream_); + } + + virtual int processZStream() { + return deflate(&stream_, Z_SYNC_FLUSH); + } + + private: + static const int COMPRESSION_LEVEL = 9; + }; +} + +#endif diff --git a/Swiften/Compress/ZLibDecompressor.h b/Swiften/Compress/ZLibDecompressor.h new file mode 100644 index 0000000..808feb2 --- /dev/null +++ b/Swiften/Compress/ZLibDecompressor.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_ZLibDecompressor_H +#define SWIFTEN_ZLibDecompressor_H + +#include <cassert> + +#include "Swiften/Compress/ZLibCodecompressor.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ZLibDecompressor : public ZLibCodecompressor { + public: + ZLibDecompressor() { + int result = inflateInit(&stream_); + assert(result == Z_OK); + (void) result; + } + + ~ZLibDecompressor() { + inflateEnd(&stream_); + } + + virtual int processZStream() { + return inflate(&stream_, Z_SYNC_FLUSH); + } + }; +} + +#endif diff --git a/Swiften/Compress/ZLibException.h b/Swiften/Compress/ZLibException.h new file mode 100644 index 0000000..f16b4ed --- /dev/null +++ b/Swiften/Compress/ZLibException.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_ZLIBEXCEPTION_H +#define SWIFTEN_ZLIBEXCEPTION_H + +namespace Swift { + class ZLibException { + public: + ZLibException() {} + }; +} + +#endif diff --git a/Swiften/Controllers/ChatController.cpp b/Swiften/Controllers/ChatController.cpp new file mode 100644 index 0000000..bac73d4 --- /dev/null +++ b/Swiften/Controllers/ChatController.cpp @@ -0,0 +1,38 @@ +#include "Swiften/Controllers/ChatController.h" + +#include "Swiften/Controllers/ChatWindow.h" +#include "Swiften/Controllers/ChatWindowFactory.h" +#include "Swiften/Controllers/NickResolver.h" + +namespace Swift { + +/** + * The controller does not gain ownership of the stanzaChannel, nor the factory. + */ +ChatController::ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle) + : ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle) { + nickResolver_ = nickResolver; +} + +bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { + return false; +} + +void ChatController::preHandleIncomingMessage(boost::shared_ptr<Message> message) { + JID from = message->getFrom(); + if (!from.equals(toJID_, JID::WithResource)) { + if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){ + toJID_ = from; + } + } +} + +void ChatController::postSendMessage(const String& body) { + chatWindow_->addMessage(body, "me", true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel() : boost::optional<SecurityLabel>()); +} + +String ChatController::senderDisplayNameFromMessage(JID from) { + return nickResolver_->jidToNick(from); +} + +} diff --git a/Swiften/Controllers/ChatController.h b/Swiften/Controllers/ChatController.h new file mode 100644 index 0000000..314bd70 --- /dev/null +++ b/Swiften/Controllers/ChatController.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_ChatController_H +#define SWIFTEN_ChatController_H + +#include "Swiften/Controllers/ChatControllerBase.h" + +namespace Swift { + class NickResolver; + class ChatController : public ChatControllerBase { + public: + ChatController(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle); + ~ChatController() {}; + //boost::signal<void (const JID&, const JID&)> onJIDChanged; + protected: + bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); + void postSendMessage(const String &body); + void preHandleIncomingMessage(boost::shared_ptr<Message> message); + String senderDisplayNameFromMessage(JID from); + private: + NickResolver* nickResolver_; + JID contact_; + }; +} +#endif + diff --git a/Swiften/Controllers/ChatControllerBase.cpp b/Swiften/Controllers/ChatControllerBase.cpp new file mode 100644 index 0000000..5f8535e --- /dev/null +++ b/Swiften/Controllers/ChatControllerBase.cpp @@ -0,0 +1,171 @@ +#include "Swiften/Controllers/ChatControllerBase.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Controllers/ChatWindow.h" +#include "Swiften/Controllers/ChatWindowFactory.h" +#include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h" + +namespace Swift { + +ChatControllerBase::ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle) : stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle) { + chatWindow_ = chatWindowFactory_->createChatWindow(toJID); + chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); + chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1)); + presenceOracle_->onPresenceChange.connect(boost::bind(&ChatControllerBase::handlePresenceChange, this, _1, _2)); +} + +ChatControllerBase::~ChatControllerBase() { + delete chatWindow_; +} + +void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { + if (info->hasFeature(DiscoInfo::SecurityLabels)) { + chatWindow_->setSecurityLabelsEnabled(true); + chatWindow_->setSecurityLabelsError(); + GetSecurityLabelsCatalogRequest* request = new GetSecurityLabelsCatalogRequest(JID(toJID_.toBare()), iqRouter_, Request::AutoDeleteAfterResponse); + request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); + request->send(); + labelsEnabled_ = true; + } else { + chatWindow_->setSecurityLabelsEnabled(false); + labelsEnabled_ = false; + } +} + +String ChatControllerBase::getStatusChangeString(boost::shared_ptr<Presence> presence) { + String nick = senderDisplayNameFromMessage(presence->getFrom()); + if (presence->getType() == Presence::Unavailable) { + return nick + " has gone offline."; + } else if (presence->getType() == Presence::Available) { + StatusShow::Type show = presence->getShow(); + if (show == StatusShow::Online || show == StatusShow::FFC) { + return nick + " has become available."; + } else if (show == StatusShow::Away || show == StatusShow::XA) { + return nick + " has gone away."; + } else if (show == StatusShow::DND) { + return nick + " is now busy."; + } + } + + return ""; +} + +void ChatControllerBase::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence) { + if (!(toJID_.isBare() && newPresence->getFrom().equals(toJID_, JID::WithoutResource)) && newPresence->getFrom() != toJID_) { + return; + } + String newStatusChangeString = getStatusChangeString(newPresence); + if (previousPresence.get() == NULL || newStatusChangeString != getStatusChangeString(previousPresence)) { + chatWindow_->addSystemMessage(newStatusChangeString); + } +} + +void ChatControllerBase::handleAllMessagesRead() { + foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) { + messageEvent->read(); + } + unreadMessages_.clear(); + chatWindow_->setUnreadMessageCount(0); +} + +void ChatControllerBase::handleSendMessageRequest(const String &body) { + if (body.isEmpty()) { + return; + } + boost::shared_ptr<Message> message(new Message()); + message->setTo(toJID_); + message->setType(Swift::Message::Chat); + message->setBody(body); + boost::optional<SecurityLabel> label; + if (labelsEnabled_) { + message->addPayload(boost::shared_ptr<SecurityLabel>(new SecurityLabel(chatWindow_->getSelectedSecurityLabel()))); + label = boost::optional<SecurityLabel>(chatWindow_->getSelectedSecurityLabel()); + } + preSendMessageRequest(message); + stanzaChannel_->sendMessage(message); + postSendMessage(message->getBody()); +} + +void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, const boost::optional<Error>& error) { + if (!error) { + if (catalog->getLabels().size() == 0) { + chatWindow_->setSecurityLabelsEnabled(false); + labelsEnabled_ = false; + } else { + chatWindow_->setAvailableSecurityLabels(catalog->getLabels()); + chatWindow_->setSecurityLabelsEnabled(true); + } + } else { + chatWindow_->setSecurityLabelsError(); + } +} + +void ChatControllerBase::showChatWindow() { + chatWindow_->show(); +} + +String ChatControllerBase::senderDisplayNameFromMessage(JID from) { + return from; +} + + + +void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { + unreadMessages_.push_back(messageEvent); + chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + + boost::shared_ptr<Message> message = messageEvent->getStanza(); + preHandleIncomingMessage(message); + String body = message->getBody(); + if (message->isError()) { + String errorMessage = getErrorMessage(message->getPayload<Error>()); + chatWindow_->addErrorMessage(errorMessage); + } + else { + showChatWindow(); + boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); + boost::optional<SecurityLabel> maybeLabel = label ? boost::optional<SecurityLabel>(*label) : boost::optional<SecurityLabel>(); + JID from = message->getFrom(); + chatWindow_->addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), maybeLabel); + } +} + +String ChatControllerBase::getErrorMessage(boost::shared_ptr<Error> error) { + String defaultMessage = "Error sending message"; + if (!error->getText().isEmpty()) { + return error->getText(); + } + else { + switch (error->getCondition()) { + case Error::BadRequest: return defaultMessage; break; + case Error::Conflict: return defaultMessage; break; + case Error::FeatureNotImplemented: return defaultMessage; break; + case Error::Forbidden: return defaultMessage; break; + case Error::Gone: return "Recipient can no longer be contacted"; break; + case Error::InternalServerError: return "Internal server error"; break; + case Error::ItemNotFound: return defaultMessage; break; + case Error::JIDMalformed: return defaultMessage; break; + case Error::NotAcceptable: return "Message was rejected"; break; + case Error::NotAllowed: return defaultMessage; break; + case Error::NotAuthorized: return defaultMessage; break; + case Error::PaymentRequired: return defaultMessage; break; + case Error::RecipientUnavailable: return "Recipient is unavailable."; break; + case Error::Redirect: return defaultMessage; break; + case Error::RegistrationRequired: return defaultMessage; break; + case Error::RemoteServerNotFound: return "Recipient's server not found."; break; + case Error::RemoteServerTimeout: return defaultMessage; break; + case Error::ResourceConstraint: return defaultMessage; break; + case Error::ServiceUnavailable: return defaultMessage; break; + case Error::SubscriptionRequired: return defaultMessage; break; + case Error::UndefinedCondition: return defaultMessage; break; + case Error::UnexpectedRequest: return defaultMessage; break; + } + } + return defaultMessage; +} + +} diff --git a/Swiften/Controllers/ChatControllerBase.h b/Swiften/Controllers/ChatControllerBase.h new file mode 100644 index 0000000..1967977 --- /dev/null +++ b/Swiften/Controllers/ChatControllerBase.h @@ -0,0 +1,57 @@ +#ifndef SWIFTEN_ChatControllerBase_H +#define SWIFTEN_ChatControllerBase_H + +#include <map> +#include <vector> +#include <boost/shared_ptr.hpp> +#include <boost/signals.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Events/MessageEvent.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/SecurityLabelsCatalog.h" +#include "Swiften/Elements/Error.h" +#include "Swiften/Presence/PresenceOracle.h" + +namespace Swift { + class IQRouter; + class StanzaChannel; + class ChatWindow; + class ChatWindowFactory; + + class ChatControllerBase { + public: + virtual ~ChatControllerBase(); + void showChatWindow(); + void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); + void handleIncomingMessage(boost::shared_ptr<MessageEvent> message); + + protected: + ChatControllerBase(StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle); + virtual void postSendMessage(const String&) {}; + virtual String senderDisplayNameFromMessage(JID from); + void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence); + virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; + virtual void preHandleIncomingMessage(boost::shared_ptr<Message>) {}; + virtual void preSendMessageRequest(boost::shared_ptr<Message>) {}; + + std::vector<boost::shared_ptr<MessageEvent> > unreadMessages_; + StanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + ChatWindowFactory* chatWindowFactory_; + ChatWindow* chatWindow_; + JID toJID_; + bool labelsEnabled_; + PresenceOracle* presenceOracle_; + + private: + void handleSendMessageRequest(const String &body); + String getStatusChangeString(boost::shared_ptr<Presence> presence); + void handleAllMessagesRead(); + void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, const boost::optional<Error>& error); + String getErrorMessage(boost::shared_ptr<Error>); + }; +} + +#endif diff --git a/Swiften/Controllers/ChatWindow.h b/Swiften/Controllers/ChatWindow.h new file mode 100644 index 0000000..04d0007 --- /dev/null +++ b/Swiften/Controllers/ChatWindow.h @@ -0,0 +1,37 @@ +#ifndef SWIFTEN_CHATWINDOW_H +#define SWIFTEN_CHATWINDOW_H + +#include <boost/optional.hpp> +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/SecurityLabel.h" + +namespace Swift { + class TreeWidget; + class ChatWindow { + public: + virtual ~ChatWindow() {}; + + virtual void addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label) = 0; + virtual void addSystemMessage(const String& message) = 0; + virtual void addErrorMessage(const String& message) = 0; + + virtual void show() = 0; + virtual void setAvailableSecurityLabels(const std::vector<SecurityLabel>& labels) = 0; + virtual void setSecurityLabelsEnabled(bool enabled) = 0; + virtual void setUnreadMessageCount(int count) = 0; + virtual void convertToMUC() = 0; + virtual TreeWidget *getTreeWidget() = 0; + virtual void setSecurityLabelsError() = 0; + virtual SecurityLabel getSelectedSecurityLabel() = 0; + + boost::signal<void ()> onClosed; + boost::signal<void ()> onAllMessagesRead; + boost::signal<void (const String&)> onSendMessageRequest; + }; +} +#endif + diff --git a/Swiften/Controllers/ChatWindowFactory.h b/Swiften/Controllers/ChatWindowFactory.h new file mode 100644 index 0000000..c55ddba --- /dev/null +++ b/Swiften/Controllers/ChatWindowFactory.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_CHATWINDOWFACTORY_H +#define SWIFTEN_CHATWINDOWFACTORY_H + +#include "Swiften/JID/JID.h" + +namespace Swift { + class ChatWindow; + + class ChatWindowFactory { + public: + virtual ~ChatWindowFactory() {}; + /** + * Transfers ownership of result. + */ + virtual ChatWindow* createChatWindow(const JID &contact) = 0; + + }; +} +#endif + diff --git a/Swiften/Controllers/EventController.cpp b/Swiften/Controllers/EventController.cpp new file mode 100644 index 0000000..a87f79a --- /dev/null +++ b/Swiften/Controllers/EventController.cpp @@ -0,0 +1,21 @@ +#include "Swiften/Controllers/EventController.h" + +#include <boost/bind.hpp> +#include <algorithm> + +namespace Swift { + +void EventController::handleIncomingEvent(boost::shared_ptr<MessageEvent> event) { + if (event->isReadable()) { + events_.push_back(event); + event->onRead.connect(boost::bind(&EventController::handleEventRead, this, event)); + onEventQueueLengthChange(events_.size()); + } +} + +void EventController::handleEventRead(boost::shared_ptr<MessageEvent> event) { + events_.erase(std::remove(events_.begin(), events_.end(), event), events_.end()); + onEventQueueLengthChange(events_.size()); +} + +} diff --git a/Swiften/Controllers/EventController.h b/Swiften/Controllers/EventController.h new file mode 100644 index 0000000..ab161af --- /dev/null +++ b/Swiften/Controllers/EventController.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_EventController_H +#define SWIFTEN_EventController_H + + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Events/MessageEvent.h" + +namespace Swift { + class EventController { + public: + void handleIncomingEvent(boost::shared_ptr<MessageEvent> event); + boost::signal<void (int)> onEventQueueLengthChange; + + private: + void handleEventRead(boost::shared_ptr<MessageEvent> event); + std::vector<boost::shared_ptr<MessageEvent> > events_; + }; +} +#endif + + diff --git a/Swiften/Controllers/LoginWindow.h b/Swiften/Controllers/LoginWindow.h new file mode 100644 index 0000000..44855c0 --- /dev/null +++ b/Swiften/Controllers/LoginWindow.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_LoginWindow_H +#define SWIFTEN_LoginWindow_H + +#include "Swiften/Base/String.h" + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + class MainWindow; + class LoginWindow { + public: + virtual ~LoginWindow() {}; + virtual void morphInto(MainWindow *mainWindow) = 0; + virtual void loggedOut() = 0; + virtual void setMessage(const String&) = 0; + + boost::signal<void (const String&, const String&, const String& /* certificateFile */, bool)> onLoginRequest; + }; +} +#endif + diff --git a/Swiften/Controllers/LoginWindowFactory.h b/Swiften/Controllers/LoginWindowFactory.h new file mode 100644 index 0000000..d52325e --- /dev/null +++ b/Swiften/Controllers/LoginWindowFactory.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_LoginWindowFactory_H +#define SWIFTEN_LoginWindowFactory_H + + + +namespace Swift { + class LoginWindow; + class String; + + class LoginWindowFactory { + public: + virtual ~LoginWindowFactory() {}; + + /** + * Transfers ownership of result. + */ + virtual LoginWindow* createLoginWindow(const String& defaultJID, const String& defaultPassword, const String& defaultCertificate) = 0; + + }; +} +#endif + diff --git a/Swiften/Controllers/MUCController.cpp b/Swiften/Controllers/MUCController.cpp new file mode 100644 index 0000000..8137fe1 --- /dev/null +++ b/Swiften/Controllers/MUCController.cpp @@ -0,0 +1,76 @@ +#include "Swiften/Controllers/MUCController.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Controllers/ChatWindow.h" +#include "Swiften/Controllers/ChatWindowFactory.h" +#include "Swiften/MUC/MUC.h" +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Roster/Roster.h" +#include "Swiften/Roster/SetPresence.h" +#include "Swiften/Roster/TreeWidgetFactory.h" + +namespace Swift { + +/** + * The controller does not gain ownership of the stanzaChannel, nor the factory. + */ +MUCController::MUCController ( + const JID &muc, + const String &nick, + StanzaChannel* stanzaChannel, + IQRouter* iqRouter, + ChatWindowFactory* chatWindowFactory, + TreeWidgetFactory *treeWidgetFactory, + PresenceOracle* presenceOracle) : + ChatControllerBase(stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle), + muc_(new MUC(stanzaChannel, muc)), + nick_(nick), + treeWidgetFactory_(treeWidgetFactory) { + roster_ = new Roster(chatWindow_->getTreeWidget(), treeWidgetFactory_); + chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); + muc_->joinAs(nick); + muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); + muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); + muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); + chatWindow_->convertToMUC(); + chatWindow_->show(); +} + +MUCController::~MUCController() { + delete muc_; + //don't crash on exit by masking this. FIXME. + //delete roster_; +} + +void MUCController::handleWindowClosed() { + muc_->part(); +} + +void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { + roster_->addContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()), occupant.getNick(), "Occupants"); +} + +void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& /*reason*/) { + roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); +} + +void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) { + roster_->applyOnItems(SetPresence(presence, JID::WithResource)); +} + +bool MUCController::isIncomingMessageFromMe(boost::shared_ptr<Message> message) { + JID from = message->getFrom(); + return nick_ == from.getResource(); +} + +String MUCController::senderDisplayNameFromMessage(JID from) { + return from.getResource(); +} + +void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) { + message->setType(Swift::Message::Groupchat); +} + +} diff --git a/Swiften/Controllers/MUCController.h b/Swiften/Controllers/MUCController.h new file mode 100644 index 0000000..a7859cf --- /dev/null +++ b/Swiften/Controllers/MUCController.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_MUCController_H +#define SWIFTEN_MUCController_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Controllers/ChatControllerBase.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/JID/JID.h" +#include "Swiften/MUC/MUC.h" +#include "Swiften/MUC/MUCOccupant.h" + +namespace Swift { + class StanzaChannel; + class IQRouter; + class ChatWindow; + class ChatWindowFactory; + class Roster; + class TreeWidgetFactory; + + class MUCController : public ChatControllerBase { + public: + MUCController(const JID &muc, const String &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory *treeWidgetFactory, PresenceOracle* presenceOracle); + ~MUCController(); + + protected: + void preSendMessageRequest(boost::shared_ptr<Message> message); + bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); + String senderDisplayNameFromMessage(JID from); + private: + void handleWindowClosed(); + void handleOccupantJoined(const MUCOccupant& occupant); + void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const String& reason); + void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence); + MUC *muc_; + String nick_; + TreeWidgetFactory *treeWidgetFactory_; + Roster *roster_; + }; +} +#endif + diff --git a/Swiften/Controllers/MainController.cpp b/Swiften/Controllers/MainController.cpp new file mode 100644 index 0000000..e0d0fe7 --- /dev/null +++ b/Swiften/Controllers/MainController.cpp @@ -0,0 +1,259 @@ +#include "Swiften/Controllers/MainController.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> +#include <stdlib.h> + +#include "Swiften/Application/Application.h" +#include "Swiften/Application/ApplicationMessageDisplay.h" +#include "Swiften/Controllers/ChatController.h" +#include "Swiften/Controllers/ChatWindowFactory.h" +#include "Swiften/Controllers/EventController.h" +#include "Swiften/Controllers/LoginWindow.h" +#include "Swiften/Controllers/LoginWindowFactory.h" +#include "Swiften/Controllers/MainWindow.h" +#include "Swiften/Controllers/MainWindowFactory.h" +#include "Swiften/Controllers/MUCController.h" +#include "Swiften/Controllers/NickResolver.h" +#include "Swiften/Controllers/RosterController.h" +#include "Swiften/Controllers/XMPPRosterController.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" +#include "Swiften/Client/Client.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Roster/XMPPRoster.h" +#include "Swiften/Queries/Responders/SoftwareVersionResponder.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Settings/SettingsProvider.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Disco/CapsInfoGenerator.h" +#include "Swiften/Queries/Requests/GetDiscoInfoRequest.h" + +namespace Swift { + +static const String CLIENT_NAME = "Swift"; +static const String CLIENT_VERSION = "0.3"; +static const String CLIENT_NODE = "http://swift.im"; + +typedef std::pair<JID, ChatController*> JIDChatControllerPair; +typedef std::pair<JID, MUCController*> JIDMUCControllerPair; + +MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, TreeWidgetFactory *treeWidgetFactory, SettingsProvider *settings, Application* application) + : client_(NULL), chatWindowFactory_(chatWindowFactory), mainWindowFactory_(mainWindowFactory), loginWindowFactory_(loginWindowFactory), treeWidgetFactory_(treeWidgetFactory), settings_(settings), + xmppRosterController_(NULL), rosterController_(NULL), loginWindow_(NULL), clientVersionResponder_(NULL), nickResolver_(NULL), discoResponder_(NULL), + serverDiscoInfo_(new DiscoInfo()), presenceOracle_(NULL) { + application_ = application; + eventController_ = new EventController(); + eventController_->onEventQueueLengthChange.connect(boost::bind(&MainController::handleEventQueueLengthChange, this, _1)); + loginWindow_ = loginWindowFactory_->createLoginWindow(settings->getStringSetting("jid"), settings->getStringSetting("pass"), settings->getStringSetting("certificate")); + loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4)); +} + +MainController::~MainController() { + delete discoResponder_; + delete clientVersionResponder_; + delete xmppRosterController_; + delete rosterController_; + foreach (JIDChatControllerPair controllerPair, chatControllers_) { + delete controllerPair.second; + } + foreach (JIDMUCControllerPair controllerPair, mucControllers_) { + delete controllerPair.second; + } + delete presenceOracle_; + delete nickResolver_; + delete client_; +} + +void MainController::handleConnected() { + delete presenceOracle_; + presenceOracle_ = new PresenceOracle(client_); + + client_->onPresenceReceived.connect(boost::bind(&MainController::handleIncomingPresence, this, _1)); + + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + + + delete nickResolver_; + nickResolver_ = new NickResolver(xmppRoster); + + delete rosterController_; + rosterController_ = new RosterController(xmppRoster, mainWindowFactory_, treeWidgetFactory_); + rosterController_->onStartChatRequest.connect(boost::bind(&MainController::handleChatRequest, this, _1)); + rosterController_->onJoinMUCRequest.connect(boost::bind(&MainController::handleJoinMUCRequest, this, _1, _2)); + rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); + + delete xmppRosterController_; + xmppRosterController_ = new XMPPRosterController(client_, xmppRoster); + + delete clientVersionResponder_; + clientVersionResponder_ = new SoftwareVersionResponder(CLIENT_NAME, CLIENT_VERSION, client_); + loginWindow_->morphInto(rosterController_->getWindow()); + + DiscoInfo discoInfo; + discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc")); + capsInfo_ = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator(CLIENT_NODE).generateCapsInfo(discoInfo))); + discoResponder_ = new DiscoInfoResponder(client_); + discoResponder_->setDiscoInfo(discoInfo); + discoResponder_->setDiscoInfo(capsInfo_->getNode() + "#" + capsInfo_->getVersion(), discoInfo); + + serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo()); + GetDiscoInfoRequest* discoInfoRequest = new GetDiscoInfoRequest(JID(), client_, Request::AutoDeleteAfterResponse); + discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2)); + discoInfoRequest->send(); + + //Send presence last to catch all the incoming presences. + boost::shared_ptr<Presence> initialPresence(new Presence()); + initialPresence->addPayload(capsInfo_); + client_->sendPresence(initialPresence); +} + +void MainController::handleEventQueueLengthChange(int count) { + application_->getApplicationMessageDisplay()->setMessage(count == 0 ? "" : boost::lexical_cast<std::string>(count).c_str()); +} + +void MainController::handleChangeStatusRequest(StatusShow::Type show, const String &statusText) { + boost::shared_ptr<Presence> presence(new Presence()); + presence->addPayload(capsInfo_); + presence->setShow(show); + presence->setStatus(statusText); + // FIXME: This is wrong. None doesn't mean unavailable + if (show == StatusShow::None) { + presence->setType(Presence::Unavailable); + } + client_->sendPresence(presence); + if (presence->getType() == Presence::Unavailable) { + logout(); + } +} + +void MainController::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + rosterController_->handleIncomingPresence(presence); +} + +void MainController::handleLoginRequest(const String &username, const String &password, const String& certificateFile, bool remember) { + loginWindow_->setMessage(""); + + settings_->storeString("jid", username); + settings_->storeString("certificate", certificateFile); + settings_->storeString("pass", remember ? password : ""); + + delete client_; + client_ = new Swift::Client(JID(username), password); + if (!certificateFile.isEmpty()) { + client_->setCertificate(certificateFile); + } + client_->onError.connect(boost::bind(&MainController::handleError, this, _1)); + client_->onConnected.connect(boost::bind(&MainController::handleConnected, this)); + client_->onMessageReceived.connect(boost::bind(&MainController::handleIncomingMessage, this, _1)); + client_->connect(); +} + +void MainController::handleError(const ClientError& error) { + String message; + switch(error.getType()) { + case ClientError::NoError: assert(false); break; + case ClientError::DomainNameResolveError: message = "Unable to find server"; break; + case ClientError::ConnectionError: message = "Error connecting to server"; break; + case ClientError::ConnectionReadError: message = "Error while receiving server data"; break; + case ClientError::XMLError: message = "Error parsing server data"; break; + case ClientError::AuthenticationFailedError: message = "Login/password invalid"; break; + case ClientError::NoSupportedAuthMechanismsError: message = "Authentication mechanisms not supported"; break; + case ClientError::UnexpectedElementError: message = "Unexpected response"; break; + case ClientError::ResourceBindError: message = "Error binding resource"; break; + case ClientError::SessionStartError: message = "Error starting session"; break; + case ClientError::TLSError: message = "Encryption error"; break; + case ClientError::ClientCertificateLoadError: message = "Error loading certificate (Invalid password?)"; break; + case ClientError::ClientCertificateError: message = "Certificate not authorized"; break; + } + loginWindow_->setMessage(message); + logout(); +} + +void MainController::logout() { + loginWindow_->loggedOut(); + + delete discoResponder_; + discoResponder_ = NULL; + delete clientVersionResponder_; + clientVersionResponder_ = NULL; + foreach (JIDChatControllerPair controllerPair, chatControllers_) { + delete controllerPair.second; + } + client_->disconnect(); + chatControllers_.clear(); + foreach (JIDMUCControllerPair controllerPair, mucControllers_) { + delete controllerPair.second; + } + mucControllers_.clear(); +} + + +void MainController::handleChatRequest(const String &contact) { + getChatController(JID(contact))->showChatWindow(); +} + +ChatController* MainController::getChatController(const JID &contact) { + JID lookupContact(contact); + if (chatControllers_.find(lookupContact) == chatControllers_.end()) { + lookupContact = JID(contact.toBare()); + } + if (chatControllers_.find(lookupContact) == chatControllers_.end()) { + chatControllers_[contact] = new ChatController(client_, client_, chatWindowFactory_, contact, nickResolver_, presenceOracle_); + chatControllers_[contact]->setAvailableServerFeatures(serverDiscoInfo_); + lookupContact = contact; + } + return chatControllers_[lookupContact]; +} + +void MainController::handleChatControllerJIDChanged(const JID& from, const JID& to) { + chatControllers_[to] = chatControllers_[from]; + chatControllers_.erase(from); +} + +void MainController::handleJoinMUCRequest(const JID &muc, const String &nick) { + mucControllers_[muc] = new MUCController(muc, nick, client_, client_, chatWindowFactory_, treeWidgetFactory_, presenceOracle_); + mucControllers_[muc]->setAvailableServerFeatures(serverDiscoInfo_); +} + +void MainController::handleIncomingMessage(boost::shared_ptr<Message> message) { + JID jid = message->getFrom(); + boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); + + // Try to deliver it to a MUC + if (message->getType() == Message::Groupchat || message->getType() == Message::Error) { + std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare()); + if (i != mucControllers_.end()) { + i->second->handleIncomingMessage(event); + return; + } + else if (message->getType() == Message::Groupchat) { + //FIXME: Error handling - groupchat messages from an unknown muc. + return; + } + } + + //if not a mucroom + eventController_->handleIncomingEvent(event); + + // FIXME: This logic should go into a chat manager + if (event->isReadable()) { + getChatController(jid)->handleIncomingMessage(event); + } +} + +void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, const boost::optional<Error>& error) { + if (!error) { + serverDiscoInfo_ = info; + foreach (JIDChatControllerPair pair, chatControllers_) { + pair.second->setAvailableServerFeatures(info); + } + foreach (JIDMUCControllerPair pair, mucControllers_) { + pair.second->setAvailableServerFeatures(info); + } + } +} + +} diff --git a/Swiften/Controllers/MainController.h b/Swiften/Controllers/MainController.h new file mode 100644 index 0000000..e09d4fa --- /dev/null +++ b/Swiften/Controllers/MainController.h @@ -0,0 +1,85 @@ +#ifndef SWIFTEN_MainController_H +#define SWIFTEN_MainController_H + + +#include "Swiften/Base/String.h" +#include "Swiften/Client/ClientError.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Elements/Error.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Settings/SettingsProvider.h" +#include "Swiften/Elements/CapsInfo.h" + + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include <vector> + +namespace Swift { + class Application; + class Client; + class ChatWindowFactory; + class ChatController; + class EventController; + class MainWindowFactory; + class MainWindow; + class NickResolver; + class RosterController; + class XMPPRosterController; + class DiscoInfoResponder; + class LoginWindow; + class EventLoop; + class SoftwareVersionResponder; + class LoginWindowFactory; + class TreeWidgetFactory; + class MUCController; + class PresenceOracle; + + class MainController { + public: + MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, TreeWidgetFactory* treeWidgetFactory, SettingsProvider *settings, Application* application); + ~MainController(); + + + private: + void handleConnected(); + void handleLoginRequest(const String& username, const String& password, const String& certificateFile, bool remember); + void handleChatRequest(const String& contact); + void handleJoinMUCRequest(const JID& muc, const String& nick); + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + void handleChatControllerJIDChanged(const JID& from, const JID& to); + void handleIncomingMessage(boost::shared_ptr<Message> message); + void handleChangeStatusRequest(StatusShow::Type show, const String &statusText); + void handleError(const ClientError& error); + void handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo>, const boost::optional<Error>&); + void handleEventQueueLengthChange(int count); + ChatController* getChatController(const JID &contact); + void logout(); + + Client* client_; + ChatWindowFactory* chatWindowFactory_; + MainWindowFactory* mainWindowFactory_; + LoginWindowFactory* loginWindowFactory_; + TreeWidgetFactory* treeWidgetFactory_; + SettingsProvider *settings_; + Application* application_; + ChatController* chatController_; + XMPPRosterController* xmppRosterController_; + RosterController* rosterController_; + EventController* eventController_; + LoginWindow* loginWindow_; + SoftwareVersionResponder* clientVersionResponder_; + NickResolver* nickResolver_; + DiscoInfoResponder* discoResponder_; + boost::shared_ptr<CapsInfo> capsInfo_; + std::map<JID, MUCController*> mucControllers_; + std::map<JID, ChatController*> chatControllers_; + boost::shared_ptr<DiscoInfo> serverDiscoInfo_; + PresenceOracle* presenceOracle_; + }; +} +#endif + diff --git a/Swiften/Controllers/MainWindow.h b/Swiften/Controllers/MainWindow.h new file mode 100644 index 0000000..081fe6e --- /dev/null +++ b/Swiften/Controllers/MainWindow.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_MainWindow_H +#define SWIFTEN_MainWindow_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/StatusShow.h" + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + class TreeWidget; + + class MainWindow { + public: + virtual ~MainWindow() {}; + virtual TreeWidget* getTreeWidget() = 0; + + boost::signal<void (const JID&)> onStartChatRequest; + boost::signal<void (const JID&, const String&)> onJoinMUCRequest; + boost::signal<void (StatusShow::Type, const String&)> onChangeStatusRequest; + boost::signal<void (bool)> onShowOfflineToggled; + }; +} +#endif + diff --git a/Swiften/Controllers/MainWindowFactory.h b/Swiften/Controllers/MainWindowFactory.h new file mode 100644 index 0000000..cf5a061 --- /dev/null +++ b/Swiften/Controllers/MainWindowFactory.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_MainWindowFactory_H +#define SWIFTEN_MainWindowFactory_H + +#include "Swiften/JID/JID.h" + +namespace Swift { + class MainWindow; + + class MainWindowFactory { + public: + virtual ~MainWindowFactory() {}; + /** + * Transfers ownership of result. + */ + virtual MainWindow* createMainWindow() = 0; + + }; +} +#endif + diff --git a/Swiften/Controllers/Makefile.inc b/Swiften/Controllers/Makefile.inc new file mode 100644 index 0000000..c51b0f3 --- /dev/null +++ b/Swiften/Controllers/Makefile.inc @@ -0,0 +1,11 @@ +SWIFTEN_SOURCES += \ + Swiften/Controllers/ChatController.cpp \ + Swiften/Controllers/ChatControllerBase.cpp \ + Swiften/Controllers/MainController.cpp \ + Swiften/Controllers/NickResolver.cpp \ + Swiften/Controllers/RosterController.cpp \ + Swiften/Controllers/XMPPRosterController.cpp \ + Swiften/Controllers/MUCController.cpp \ + Swiften/Controllers/EventController.cpp + +include Swiften/Controllers/UnitTest/Makefile.inc diff --git a/Swiften/Controllers/NickResolver.cpp b/Swiften/Controllers/NickResolver.cpp new file mode 100644 index 0000000..1c51cb7 --- /dev/null +++ b/Swiften/Controllers/NickResolver.cpp @@ -0,0 +1,22 @@ +#include "Swiften/Controllers/NickResolver.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Roster/XMPPRoster.h" + +namespace Swift { + +NickResolver::NickResolver(boost::shared_ptr<XMPPRoster> xmppRoster) { + xmppRoster_ = xmppRoster; +} + +String NickResolver::jidToNick(const JID& jid) { + if (xmppRoster_->containsJID(jid)) { + return xmppRoster_->getNameForJID(jid); + } + std::map<JID, String>::iterator it = map_.find(jid); + return (it == map_.end()) ? jid.toBare() : it->second; +} + +} + diff --git a/Swiften/Controllers/NickResolver.h b/Swiften/Controllers/NickResolver.h new file mode 100644 index 0000000..b7dc005 --- /dev/null +++ b/Swiften/Controllers/NickResolver.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_NickResolver_H +#define SWIFTEN_NickResolver_H + +#include <map> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class XMPPRoster; + class NickResolver { + public: + NickResolver(boost::shared_ptr<XMPPRoster> xmppRoster); + String jidToNick(const JID& jid); + + private: + std::map<JID, String> map_; + boost::shared_ptr<XMPPRoster> xmppRoster_; + }; +} +#endif + + diff --git a/Swiften/Controllers/RosterController.cpp b/Swiften/Controllers/RosterController.cpp new file mode 100644 index 0000000..1efbeea --- /dev/null +++ b/Swiften/Controllers/RosterController.cpp @@ -0,0 +1,83 @@ +#include "Swiften/Controllers/RosterController.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Controllers/MainWindow.h" +#include "Swiften/Controllers/MainWindowFactory.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Roster/Roster.h" +#include "Swiften/Roster/SetPresence.h" +#include "Swiften/Roster/OfflineRosterFilter.h" +#include "Swiften/Roster/OpenChatRosterAction.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/XMPPRoster.h" + +namespace Swift { + +/** + * The controller does not gain ownership of these parameters. + */ +RosterController::RosterController(boost::shared_ptr<XMPPRoster> xmppRoster, MainWindowFactory* mainWindowFactory, TreeWidgetFactory* treeWidgetFactory) + : xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), treeWidgetFactory_(treeWidgetFactory), mainWindow_(mainWindowFactory_->createMainWindow()), roster_(new Roster(mainWindow_->getTreeWidget(), treeWidgetFactory_)), offlineFilter_(new OfflineRosterFilter()) { + roster_->addFilter(offlineFilter_); + mainWindow_->onStartChatRequest.connect(boost::bind(&RosterController::handleStartChatRequest, this, _1)); + mainWindow_->onJoinMUCRequest.connect(boost::bind(&RosterController::handleJoinMUCRequest, this, _1, _2)); + mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2)); + mainWindow_->onShowOfflineToggled.connect(boost::bind(&RosterController::handleShowOfflineToggled, this, _1)); + roster_->onUserAction.connect(boost::bind(&RosterController::handleUserAction, this, _1)); + xmppRoster_->onJIDAdded.connect(boost::bind(&RosterController::handleOnJIDAdded, this, _1)); +} + +RosterController::~RosterController() { + delete offlineFilter_; + +} + +void RosterController::handleShowOfflineToggled(bool state) { + if (state) { + roster_->removeFilter(offlineFilter_); + } else { + roster_->addFilter(offlineFilter_); + } +} + +void RosterController::handleChangeStatusRequest(StatusShow::Type show, const String &statusText) { + onChangeStatusRequest(show, statusText); +} + +void RosterController::handleUserAction(boost::shared_ptr<UserRosterAction> action) { + boost::shared_ptr<OpenChatRosterAction> chatAction = boost::dynamic_pointer_cast<OpenChatRosterAction>(action); + if (chatAction.get() != NULL) { + ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(chatAction->getRosterItem()); + assert(contactItem); + onStartChatRequest(contactItem->getJID().toBare()); + } +} + +void RosterController::handleOnJIDAdded(const JID& jid) { + std::vector<String> groups = xmppRoster_->getGroupsForJID(jid); + String name = xmppRoster_->getNameForJID(jid); + if (!groups.empty()) { + foreach(const String& group, groups) { + roster_->addContact(jid, name, group); + } + } else { + roster_->addContact(jid, name, "Contacts"); + } +} + +void RosterController::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + roster_->applyOnItems(SetPresence(presence)); +} + +void RosterController::handleStartChatRequest(const JID& contact) { + onStartChatRequest(contact); +} + +void RosterController::handleJoinMUCRequest(const JID &muc, const String &nick) { + onJoinMUCRequest(JID(muc), nick); +} + +} diff --git a/Swiften/Controllers/RosterController.h b/Swiften/Controllers/RosterController.h new file mode 100644 index 0000000..945b068 --- /dev/null +++ b/Swiften/Controllers/RosterController.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_RosterController_H +#define SWIFTEN_RosterController_H + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Roster/UserRosterAction.h" + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + class IQRouter; + class Roster; + class XMPPRoster; + class MainWindow; + class MainWindowFactory; + class TreeWidgetFactory; + class OfflineRosterFilter; + + class RosterController { + public: + RosterController(boost::shared_ptr<XMPPRoster> xmppRoster, MainWindowFactory *mainWindowFactory, TreeWidgetFactory *treeWidgetFactory); + ~RosterController(); + void showRosterWindow(); + MainWindow* getWindow() {return mainWindow_;}; + boost::signal<void (const JID&)> onStartChatRequest; + boost::signal<void (const JID&, const String&)> onJoinMUCRequest; + boost::signal<void (StatusShow::Type, const String&)> onChangeStatusRequest; + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + + private: + void handleOnJIDAdded(const JID &jid); + void handleStartChatRequest(const JID& contact); + void handleJoinMUCRequest(const JID &muc, const String &nick); + void handleUserAction(boost::shared_ptr<UserRosterAction> action); + void handleChangeStatusRequest(StatusShow::Type show, const String &statusText); + void handleShowOfflineToggled(bool state); + boost::shared_ptr<XMPPRoster> xmppRoster_; + MainWindowFactory* mainWindowFactory_; + TreeWidgetFactory* treeWidgetFactory_; + MainWindow* mainWindow_; + Roster* roster_; + OfflineRosterFilter* offlineFilter_; + }; +} +#endif + diff --git a/Swiften/Controllers/UnitTest/Makefile.inc b/Swiften/Controllers/UnitTest/Makefile.inc new file mode 100644 index 0000000..163da01 --- /dev/null +++ b/Swiften/Controllers/UnitTest/Makefile.inc @@ -0,0 +1,4 @@ +UNITTEST_SOURCES += \ + Swiften/Controllers/UnitTest/NickResolverTest.cpp \ + Swiften/Controllers/UnitTest/XMPPRosterControllerTest.cpp + diff --git a/Swiften/Controllers/UnitTest/NickResolverTest.cpp b/Swiften/Controllers/UnitTest/NickResolverTest.cpp new file mode 100644 index 0000000..9c89d4d --- /dev/null +++ b/Swiften/Controllers/UnitTest/NickResolverTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Controllers/NickResolver.h" +#include "Swiften/Roster/XMPPRoster.h" + +using namespace Swift; + +class NickResolverTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(NickResolverTest); + CPPUNIT_TEST(testNoMatch); + CPPUNIT_TEST(testMatch); + CPPUNIT_TEST(testOverwrittenMatch); + CPPUNIT_TEST(testRemovedMatch); + CPPUNIT_TEST_SUITE_END(); + + std::vector<String> groups_; + + public: + NickResolverTest() {} + + void testNoMatch() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + NickResolver resolver(xmppRoster); + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar"), resolver.jidToNick(testling)); + } + + void testMatch() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + NickResolver resolver(xmppRoster); + JID testling("foo@bar/baz"); + xmppRoster->addContact(testling, "Test", groups_); + + CPPUNIT_ASSERT_EQUAL(String("Test"), resolver.jidToNick(testling)); + } + + void testOverwrittenMatch() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + NickResolver resolver(xmppRoster); + JID testling("foo@bar/baz"); + xmppRoster->addContact(testling, "FailTest", groups_); + xmppRoster->addContact(testling, "Test", groups_); + + CPPUNIT_ASSERT_EQUAL(String("Test"), resolver.jidToNick(testling)); + } + + void testRemovedMatch() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + NickResolver resolver(xmppRoster); + JID testling("foo@bar/baz"); + xmppRoster->addContact(testling, "FailTest", groups_); + xmppRoster->removeContact(testling); + CPPUNIT_ASSERT_EQUAL(String("foo@bar"), resolver.jidToNick(testling)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(NickResolverTest); + diff --git a/Swiften/Controllers/UnitTest/XMPPRosterControllerTest.cpp b/Swiften/Controllers/UnitTest/XMPPRosterControllerTest.cpp new file mode 100644 index 0000000..3bff8f3 --- /dev/null +++ b/Swiften/Controllers/UnitTest/XMPPRosterControllerTest.cpp @@ -0,0 +1,83 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Controllers/XMPPRosterController.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/RosterItemPayload.h" +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Roster/XMPPRoster.h" + +using namespace Swift; + +class XMPPRosterControllerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPRosterControllerTest); + CPPUNIT_TEST(testAdd); + CPPUNIT_TEST(testModify); + CPPUNIT_TEST(testRemove); + CPPUNIT_TEST_SUITE_END(); + + DummyIQChannel* channel_; + IQRouter* router_; + public: + XMPPRosterControllerTest() : channel_(new DummyIQChannel()), router_(new IQRouter(channel_)) {} + + ~XMPPRosterControllerTest() { + delete channel_; + delete router_; + } + + void testAdd() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + XMPPRosterController controller(router_, xmppRoster); + JID testling("foo@bar"); + CPPUNIT_ASSERT(!xmppRoster->containsJID(testling)); + boost::shared_ptr<Payload> payload(new RosterPayload()); + RosterItemPayload item(testling, "Bob", RosterItemPayload::Both); + dynamic_cast<RosterPayload*>(payload.get())->addItem(item); + controller.handleIQ(IQ::createRequest(IQ::Set, JID(), "eou", payload)); + CPPUNIT_ASSERT(xmppRoster->containsJID(testling)); + } + + void testModify() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + XMPPRosterController controller(router_, xmppRoster); + JID testling("foo@bar"); + CPPUNIT_ASSERT(!xmppRoster->containsJID(testling)); + boost::shared_ptr<Payload> payload1(new RosterPayload()); + RosterItemPayload item1(testling, "Bob", RosterItemPayload::Both); + dynamic_cast<RosterPayload*>(payload1.get())->addItem(item1); + controller.handleIQ(IQ::createRequest(IQ::Set, JID(), "eou", payload1)); + CPPUNIT_ASSERT(xmppRoster->containsJID(testling)); + CPPUNIT_ASSERT_EQUAL(String("Bob"), xmppRoster->getNameForJID(testling)); + boost::shared_ptr<Payload> payload2(new RosterPayload()); + RosterItemPayload item2(testling, "Bob2", RosterItemPayload::Both); + dynamic_cast<RosterPayload*>(payload2.get())->addItem(item2); + controller.handleIQ(IQ::createRequest(IQ::Set, JID(), "eou", payload2)); + CPPUNIT_ASSERT_EQUAL(String("Bob2"), xmppRoster->getNameForJID(testling)); + } + + void testRemove() { + boost::shared_ptr<XMPPRoster> xmppRoster(new XMPPRoster()); + XMPPRosterController controller(router_, xmppRoster); + JID testling("foo@bar"); + CPPUNIT_ASSERT(!xmppRoster->containsJID(testling)); + boost::shared_ptr<Payload> payload1(new RosterPayload()); + RosterItemPayload item1(testling, "Bob", RosterItemPayload::Both); + dynamic_cast<RosterPayload*>(payload1.get())->addItem(item1); + controller.handleIQ(IQ::createRequest(IQ::Set, JID(), "eou", payload1)); + CPPUNIT_ASSERT(xmppRoster->containsJID(testling)); + boost::shared_ptr<Payload> payload2(new RosterPayload()); + RosterItemPayload item2(testling, "Bob", RosterItemPayload::Remove); + dynamic_cast<RosterPayload*>(payload2.get())->addItem(item2); + controller.handleIQ(IQ::createRequest(IQ::Set, JID(), "eou", payload2)); + CPPUNIT_ASSERT(!xmppRoster->containsJID(testling)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPRosterControllerTest); + + diff --git a/Swiften/Controllers/XMPPRosterController.cpp b/Swiften/Controllers/XMPPRosterController.cpp new file mode 100644 index 0000000..b6c36d7 --- /dev/null +++ b/Swiften/Controllers/XMPPRosterController.cpp @@ -0,0 +1,54 @@ +#include "Swiften/Controllers/XMPPRosterController.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Controllers/MainWindow.h" +#include "Swiften/Controllers/MainWindowFactory.h" +#include "Swiften/Elements/RosterItemPayload.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Roster/Roster.h" +#include "Swiften/Roster/SetPresence.h" +#include "Swiften/Roster/OfflineRosterFilter.h" +#include "Swiften/Roster/OpenChatRosterAction.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/XMPPRoster.h" + +namespace Swift { + +/** + * The controller does not gain ownership of these parameters. + */ +XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, boost::shared_ptr<XMPPRoster> xmppRoster) + : IQHandler(iqRouter), xmppRoster_(xmppRoster) { + GetRosterRequest* rosterRequest = new GetRosterRequest(iqRouter, Request::AutoDeleteAfterResponse); + rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1)); + rosterRequest->send(); +} + +XMPPRosterController::~XMPPRosterController() { + +} + +void XMPPRosterController::handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload) { + foreach(const RosterItemPayload& item, rosterPayload->getItems()) { + if (item.getSubscription() == RosterItemPayload::Remove) { + xmppRoster_->removeContact(item.getJID()); + } else { + xmppRoster_->addContact(item.getJID(), item.getName(), item.getGroups()); + } + } +} + +bool XMPPRosterController::handleIQ(boost::shared_ptr<IQ> iq) { + if (iq->getType() != IQ::Set || iq->getPayload<RosterPayload>().get() == NULL || iq->getFrom().isValid()) { + return false; + } + handleRosterReceived(iq->getPayload<RosterPayload>()); + return true; +} + +} + diff --git a/Swiften/Controllers/XMPPRosterController.h b/Swiften/Controllers/XMPPRosterController.h new file mode 100644 index 0000000..2eaa6f5 --- /dev/null +++ b/Swiften/Controllers/XMPPRosterController.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Queries/IQHandler.h" + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + class IQRouter; + class XMPPRoster; + + class XMPPRosterController : public IQHandler { + public: + XMPPRosterController(IQRouter *iqRouter, boost::shared_ptr<XMPPRoster> xmppRoster); + ~XMPPRosterController(); + boost::shared_ptr<XMPPRoster> getXMPPRoster() {return xmppRoster_;}; + bool handleIQ(boost::shared_ptr<IQ>); + + private: + void handleRosterReceived(boost::shared_ptr<RosterPayload> rosterPayload); + boost::shared_ptr<XMPPRoster> xmppRoster_; + }; +} + diff --git a/Swiften/Disco/CapsInfoGenerator.cpp b/Swiften/Disco/CapsInfoGenerator.cpp new file mode 100644 index 0000000..339d76e --- /dev/null +++ b/Swiften/Disco/CapsInfoGenerator.cpp @@ -0,0 +1,34 @@ +#include "Swiften/Disco/CapsInfoGenerator.h" + +#include <algorithm> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +CapsInfoGenerator::CapsInfoGenerator(const String& node) : node_(node) { +} + +CapsInfo CapsInfoGenerator::generateCapsInfo(const DiscoInfo& discoInfo) const { + String serializedCaps; + + std::vector<DiscoInfo::Identity> identities(discoInfo.getIdentities()); + std::sort(identities.begin(), identities.end()); + foreach (const DiscoInfo::Identity& identity, identities) { + serializedCaps += identity.getCategory() + "/" + identity.getType() + "/" + identity.getLanguage() + "/" + identity.getName() + "<"; + } + + std::vector<String> features(discoInfo.getFeatures()); + std::sort(features.begin(), features.end()); + foreach (const String& feature, features) { + serializedCaps += feature + "<"; + } + + String version(Base64::encode(SHA1::getBinaryHash(serializedCaps))); + return CapsInfo(node_, version, "sha-1"); +} + +} diff --git a/Swiften/Disco/CapsInfoGenerator.h b/Swiften/Disco/CapsInfoGenerator.h new file mode 100644 index 0000000..66949ab --- /dev/null +++ b/Swiften/Disco/CapsInfoGenerator.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_CapsInfoGenerator_H +#define SWIFTEN_CapsInfoGenerator_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/CapsInfo.h" + +namespace Swift { + class DiscoInfo; + + class CapsInfoGenerator { + public: + CapsInfoGenerator(const String& node); + + CapsInfo generateCapsInfo(const DiscoInfo& discoInfo) const; + + private: + String node_; + }; +} + +#endif diff --git a/Swiften/Disco/Makefile.inc b/Swiften/Disco/Makefile.inc new file mode 100644 index 0000000..5aeba07 --- /dev/null +++ b/Swiften/Disco/Makefile.inc @@ -0,0 +1,4 @@ +SWIFTEN_SOURCES += \ + Swiften/Disco/CapsInfoGenerator.cpp + +include Swiften/Disco/UnitTest/Makefile.inc diff --git a/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp b/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp new file mode 100644 index 0000000..94b9913 --- /dev/null +++ b/Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Disco/CapsInfoGenerator.h" + +using namespace Swift; + +class CapsInfoGeneratorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(CapsInfoGeneratorTest); + CPPUNIT_TEST(testGenerate_XEP0115SimpleExample); + CPPUNIT_TEST_SUITE_END(); + + public: + CapsInfoGeneratorTest() {} + + void testGenerate_XEP0115SimpleExample() { + DiscoInfo discoInfo; + discoInfo.addIdentity(DiscoInfo::Identity("Exodus 0.9.1", "client", "pc")); + discoInfo.addFeature("http://jabber.org/protocol/disco#items"); + discoInfo.addFeature("http://jabber.org/protocol/caps"); + discoInfo.addFeature("http://jabber.org/protocol/disco#info"); + discoInfo.addFeature("http://jabber.org/protocol/muc"); + + CapsInfoGenerator testling("http://code.google.com/p/exodus"); + CapsInfo result = testling.generateCapsInfo(discoInfo); + + CPPUNIT_ASSERT_EQUAL(String("http://code.google.com/p/exodus"), result.getNode()); + CPPUNIT_ASSERT_EQUAL(String("sha-1"), result.getHash()); + CPPUNIT_ASSERT_EQUAL(String("QgayPKawpkPSDYmwT/WM94uAlu0="), result.getVersion()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CapsInfoGeneratorTest); diff --git a/Swiften/Disco/UnitTest/Makefile.inc b/Swiften/Disco/UnitTest/Makefile.inc new file mode 100644 index 0000000..928664c --- /dev/null +++ b/Swiften/Disco/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Disco/UnitTest/CapsInfoGeneratorTest.cpp diff --git a/Swiften/Elements/AuthFailure.h b/Swiften/Elements/AuthFailure.h new file mode 100644 index 0000000..b1857b3 --- /dev/null +++ b/Swiften/Elements/AuthFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_AuthFailure_H +#define SWIFTEN_AuthFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthFailure : public Element { + public: + AuthFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/AuthRequest.h b/Swiften/Elements/AuthRequest.h new file mode 100644 index 0000000..1f49175 --- /dev/null +++ b/Swiften/Elements/AuthRequest.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_AuthRequest_H +#define SWIFTEN_AuthRequest_H + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthRequest : public Element { + public: + AuthRequest(const String& mechanism = "", const ByteArray& message = "") : + mechanism_(mechanism), message_(message) { + } + + const ByteArray& getMessage() const { + return message_; + } + + void setMessage(const ByteArray& message) { + message_ = message; + } + + const String& getMechanism() const { + return mechanism_; + } + + void setMechanism(const String& mechanism) { + mechanism_ = mechanism; + } + + private: + String mechanism_; + ByteArray message_; + }; +} + +#endif diff --git a/Swiften/Elements/AuthSuccess.h b/Swiften/Elements/AuthSuccess.h new file mode 100644 index 0000000..f63d0a8 --- /dev/null +++ b/Swiften/Elements/AuthSuccess.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_AuthSuccess_H +#define SWIFTEN_AuthSuccess_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class AuthSuccess : public Element { + public: + AuthSuccess() {} + }; +} + +#endif diff --git a/Swiften/Elements/Body.h b/Swiften/Elements/Body.h new file mode 100644 index 0000000..d78cecf --- /dev/null +++ b/Swiften/Elements/Body.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_Body_H +#define SWIFTEN_Body_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Body : public Payload { + public: + Body(const String& text = "") : text_(text) { + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Elements/CapsInfo.h b/Swiften/Elements/CapsInfo.h new file mode 100644 index 0000000..4b478aa --- /dev/null +++ b/Swiften/Elements/CapsInfo.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_CapsInfo_H +#define SWIFTEN_CapsInfo_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class CapsInfo : public Payload { + public: + CapsInfo(const String& node, const String& version, const String& hash = "sha-1") : node_(node), version_(version), hash_(hash) {} + + const String& getNode() const { return node_; } + const String& getVersion() const { return version_; } + const String& getHash() const { return hash_; } + + private: + String node_; + String version_; + String hash_; + }; +} + +#endif diff --git a/Swiften/Elements/CompressFailure.h b/Swiften/Elements/CompressFailure.h new file mode 100644 index 0000000..880f71a --- /dev/null +++ b/Swiften/Elements/CompressFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_CompressFailure_H +#define SWIFTEN_CompressFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class CompressFailure : public Element { + public: + CompressFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/CompressRequest.h b/Swiften/Elements/CompressRequest.h new file mode 100644 index 0000000..53c0805 --- /dev/null +++ b/Swiften/Elements/CompressRequest.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_CompressRequest_H +#define SWIFTEN_CompressRequest_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class CompressRequest : public Element + { + public: + CompressRequest(const String& method = "") : method_(method) {} + + const String& getMethod() const { + return method_; + } + + void setMethod(const String& method) { + method_ = method; + } + + private: + String method_; + }; +} + +#endif diff --git a/Swiften/Elements/Compressed.h b/Swiften/Elements/Compressed.h new file mode 100644 index 0000000..37113d8 --- /dev/null +++ b/Swiften/Elements/Compressed.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_COMPRESSED_H +#define SWIFTEN_COMPRESSED_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class Compressed : public Element + { + public: + Compressed() {} + }; +} + +#endif diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp new file mode 100644 index 0000000..63ee051 --- /dev/null +++ b/Swiften/Elements/DiscoInfo.cpp @@ -0,0 +1,25 @@ +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + +bool DiscoInfo::Identity::operator<(const Identity& other) const { + if (category_ == other.category_) { + if (type_ == other.type_) { + if (lang_ == other.lang_) { + return name_ < other.name_; + } + else { + return lang_ < other.lang_; + } + } + else { + return type_ < other.type_; + } + } + else { + return category_ < other.category_; + } +} + +const std::string DiscoInfo::SecurityLabels = "urn:xmpp:sec-label:0"; +} diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h new file mode 100644 index 0000000..8caeaf9 --- /dev/null +++ b/Swiften/Elements/DiscoInfo.h @@ -0,0 +1,83 @@ +#ifndef SWIFTEN_DiscoInfo_H +#define SWIFTEN_DiscoInfo_H + +#include <vector> +#include <algorithm> + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class DiscoInfo : public Payload { + public: + const static std::string SecurityLabels; + class Identity { + public: + Identity(const String& name, const String& category = "client", const String& type = "pc", const String& lang = "") : name_(name), category_(category), type_(type), lang_(lang) { + } + + const String& getCategory() const { + return category_; + } + + const String& getType() const { + return type_; + } + + const String& getLanguage() const { + return lang_; + } + + const String& getName() const { + return name_; + } + + // Sorted according to XEP-115 rules + bool operator<(const Identity& other) const; + + private: + String name_; + String category_; + String type_; + String lang_; + }; + + DiscoInfo() { + } + + const String& getNode() const { + return node_; + } + + void setNode(const String& node) { + node_ = node; + } + + const std::vector<Identity> getIdentities() const { + return identities_; + } + + void addIdentity(const Identity& identity) { + identities_.push_back(identity); + } + + const std::vector<String>& getFeatures() const { + return features_; + } + + void addFeature(const String& feature) { + features_.push_back(feature); + } + + bool hasFeature(const String& feature) const { + return std::find(features_.begin(), features_.end(), feature) != features_.end(); + } + + private: + String node_; + std::vector<Identity> identities_; + std::vector<String> features_; + }; +} + +#endif diff --git a/Swiften/Elements/Element.cpp b/Swiften/Elements/Element.cpp new file mode 100644 index 0000000..a62aad9 --- /dev/null +++ b/Swiften/Elements/Element.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Elements/Element.h" + +namespace Swift { + +Element::~Element() { +} + +} diff --git a/Swiften/Elements/Element.h b/Swiften/Elements/Element.h new file mode 100644 index 0000000..d1e9c6a --- /dev/null +++ b/Swiften/Elements/Element.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_ELEMENT_H +#define SWIFTEN_ELEMENT_H + +namespace Swift { + class Element { + public: + virtual ~Element(); + }; +} + +#endif diff --git a/Swiften/Elements/Error.h b/Swiften/Elements/Error.h new file mode 100644 index 0000000..8793f35 --- /dev/null +++ b/Swiften/Elements/Error.h @@ -0,0 +1,70 @@ +#ifndef SWIFTEN_Error_H +#define SWIFTEN_Error_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Error : public Payload { + public: + enum Type { Cancel, Continue, Modify, Auth, Wait }; + + enum Condition { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + Gone, + InternalServerError, + ItemNotFound, + JIDMalformed, + NotAcceptable, + NotAllowed, + NotAuthorized, + PaymentRequired, + RecipientUnavailable, + Redirect, + RegistrationRequired, + RemoteServerNotFound, + RemoteServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + Error(Condition condition = UndefinedCondition, Type type = Cancel, const String& text = String()) : type_(type), condition_(condition), text_(text) { } + + Type getType() const { + return type_; + } + + void setType(Type type) { + type_ = type; + } + + Condition getCondition() const { + return condition_; + } + + void setCondition(Condition condition) { + condition_ = condition; + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + Type type_; + Condition condition_; + String text_; + }; +} + +#endif diff --git a/Swiften/Elements/IQ.cpp b/Swiften/Elements/IQ.cpp new file mode 100644 index 0000000..51f4745 --- /dev/null +++ b/Swiften/Elements/IQ.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Elements/IQ.h" + +namespace Swift { + +boost::shared_ptr<IQ> IQ::createRequest( + Type type, const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + boost::shared_ptr<IQ> iq(new IQ(type)); + if (to.isValid()) { + iq->setTo(to); + } + iq->setID(id); + if (payload) { + iq->addPayload(payload); + } + return iq; +} + +boost::shared_ptr<IQ> IQ::createResult( + const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + boost::shared_ptr<IQ> iq(new IQ(Result)); + iq->setTo(to); + iq->setID(id); + if (payload) { + iq->addPayload(payload); + } + return iq; +} + +boost::shared_ptr<IQ> IQ::createError(const JID& to, const String& id, Error::Condition condition, Error::Type type) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Error)); + iq->setTo(to); + iq->setID(id); + iq->addPayload(boost::shared_ptr<Swift::Error>(new Swift::Error(condition, type))); + return iq; +} + +} diff --git a/Swiften/Elements/IQ.h b/Swiften/Elements/IQ.h new file mode 100644 index 0000000..231439f --- /dev/null +++ b/Swiften/Elements/IQ.h @@ -0,0 +1,39 @@ +#ifndef SWIFTEN_IQ_H +#define SWIFTEN_IQ_H + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Error.h" + +namespace Swift +{ + class IQ : public Stanza + { + public: + enum Type { Get, Set, Result, Error }; + + IQ(Type type = Get) : type_(type) { } + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + static boost::shared_ptr<IQ> createRequest( + Type type, + const JID& to, + const String& id, + boost::shared_ptr<Payload> payload); + static boost::shared_ptr<IQ> createResult( + const JID& to, + const String& id, + boost::shared_ptr<Payload> payload = boost::shared_ptr<Payload>()); + static boost::shared_ptr<IQ> createError( + const JID& to, + const String& id, + Error::Condition condition, + Error::Type type); + + private: + Type type_; + }; +} + +#endif diff --git a/Swiften/Elements/MUCPayload.cpp b/Swiften/Elements/MUCPayload.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Swiften/Elements/MUCPayload.cpp diff --git a/Swiften/Elements/MUCPayload.h b/Swiften/Elements/MUCPayload.h new file mode 100644 index 0000000..205ae46 --- /dev/null +++ b/Swiften/Elements/MUCPayload.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_MUCPayload_H +#define SWIFTEN_MUCPayload_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class MUCPayload : public Payload + { + public: + MUCPayload() { } + + }; +} + +#endif diff --git a/Swiften/Elements/Makefile.inc b/Swiften/Elements/Makefile.inc new file mode 100644 index 0000000..aceaac0 --- /dev/null +++ b/Swiften/Elements/Makefile.inc @@ -0,0 +1,9 @@ +SWIFTEN_SOURCES += \ + Swiften/Elements/RosterPayload.cpp \ + Swiften/Elements/Payload.cpp \ + Swiften/Elements/Stanza.cpp \ + Swiften/Elements/Element.cpp \ + Swiften/Elements/DiscoInfo.cpp \ + Swiften/Elements/IQ.cpp + +include Swiften/Elements/UnitTest/Makefile.inc diff --git a/Swiften/Elements/Message.h b/Swiften/Elements/Message.h new file mode 100644 index 0000000..a49f496 --- /dev/null +++ b/Swiften/Elements/Message.h @@ -0,0 +1,46 @@ +#ifndef SWIFTEN_STANZAS_MESSAGE_H +#define SWIFTEN_STANZAS_MESSAGE_H + +#include <boost/optional.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Body.h" +#include "Swiften/Elements/Error.h" +#include "Swiften/Elements/Stanza.h" + +namespace Swift +{ + class Message : public Stanza + { + public: + enum Type { Normal, Chat, Error, Groupchat, Headline }; + + Message() : type_(Chat) { } + + String getBody() const { + boost::shared_ptr<Body> body(getPayload<Body>()); + if (body) { + return body->getText(); + } + return ""; + } + + void setBody(const String& body) { + updatePayload(boost::shared_ptr<Body>(new Body(body))); + } + + bool isError() { + boost::shared_ptr<Swift::Error> error(getPayload<Swift::Error>()); + return getType() == Message::Error || error.get() != NULL; + } + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + private: + String body_; + Type type_; + }; +} + +#endif diff --git a/Swiften/Elements/Payload.cpp b/Swiften/Elements/Payload.cpp new file mode 100644 index 0000000..d929fad --- /dev/null +++ b/Swiften/Elements/Payload.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Elements/Payload.h" + +namespace Swift { + +Payload::~Payload() { +} + +} diff --git a/Swiften/Elements/Payload.h b/Swiften/Elements/Payload.h new file mode 100644 index 0000000..829dc24 --- /dev/null +++ b/Swiften/Elements/Payload.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_PAYLOAD_H +#define SWIFTEN_PAYLOAD_H + +namespace Swift { + class Payload { + public: + virtual ~Payload(); + }; +} + +#endif diff --git a/Swiften/Elements/Presence.h b/Swiften/Elements/Presence.h new file mode 100644 index 0000000..1243bca --- /dev/null +++ b/Swiften/Elements/Presence.h @@ -0,0 +1,58 @@ +#ifndef SWIFTEN_Presence +#define SWIFTEN_Presence + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Status.h" +#include "Swiften/Elements/StatusShow.h" +#include "Swiften/Elements/Priority.h" + +namespace Swift { + class Presence : public Stanza + { + public: + enum Type { Available, Error, Probe, Subscribe, Subscribed, Unavailable, Unsubscribe, Unsubscribed }; + + Presence() : type_(Available) /*, showType_(Online)*/ {} + + Type getType() const { return type_; } + void setType(Type type) { type_ = type; } + + StatusShow::Type getShow() const { + boost::shared_ptr<StatusShow> show(getPayload<StatusShow>()); + if (show) { + return show->getType(); + } + return type_ == Available ? StatusShow::Online : StatusShow::None; + } + + void setShow(const StatusShow::Type &show) { + updatePayload(boost::shared_ptr<StatusShow>(new StatusShow(show))); + } + + String getStatus() const { + boost::shared_ptr<Status> status(getPayload<Status>()); + if (status) { + return status->getText(); + } + return ""; + } + + void setStatus(const String& status) { + updatePayload(boost::shared_ptr<Status>(new Status(status))); + } + + int getPriority() const { + boost::shared_ptr<Priority> priority(getPayload<Priority>()); + return (priority ? priority->getPriority() : 0); + } + + void setPriority(int priority) { + updatePayload(boost::shared_ptr<Priority>(new Priority(priority))); + } + + private: + Presence::Type type_; + }; +} + +#endif diff --git a/Swiften/Elements/Priority.h b/Swiften/Elements/Priority.h new file mode 100644 index 0000000..fd6080e --- /dev/null +++ b/Swiften/Elements/Priority.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_Priority_H +#define SWIFTEN_Priority_H + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class Priority : public Payload { + public: + Priority(int priority = 0) : priority_(priority) { + } + + void setPriority(int priority) { + priority_ = priority; + } + + int getPriority() const { + return priority_; + } + + private: + int priority_; + }; +} + +#endif diff --git a/Swiften/Elements/ResourceBind.h b/Swiften/Elements/ResourceBind.h new file mode 100644 index 0000000..3b6c632 --- /dev/null +++ b/Swiften/Elements/ResourceBind.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_ResourceBind_H +#define SWIFTEN_ResourceBind_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class ResourceBind : public Payload + { + public: + ResourceBind() {} + + void setJID(const JID& jid) { + jid_ = jid; + } + + const JID& getJID() const { + return jid_; + } + + void setResource(const String& resource) { + resource_ = resource; + } + + const String& getResource() const { + return resource_; + } + + private: + JID jid_; + String resource_; + }; +} + +#endif diff --git a/Swiften/Elements/RosterItemPayload.h b/Swiften/Elements/RosterItemPayload.h new file mode 100644 index 0000000..3925117 --- /dev/null +++ b/Swiften/Elements/RosterItemPayload.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_RosterItemPayloadPayload_H +#define SWIFTEN_RosterItemPayloadPayload_H + +#include <vector> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class RosterItemPayload + { + public: + enum Subscription { None, To, From, Both, Remove }; + + RosterItemPayload() : subscription_(None), ask_(false) {} + RosterItemPayload(const JID& jid, const String& name, Subscription subscription) : jid_(jid), name_(name), subscription_(subscription), ask_(false) { } + + void setJID(const JID& jid) { jid_ = jid; } + const JID& getJID() const { return jid_; } + + void setName(const String& name) { name_ = name; } + const String& getName() const { return name_; } + + void setSubscription(Subscription subscription) { subscription_ = subscription; } + const Subscription& getSubscription() const { return subscription_; } + + void addGroup(const String& group) { groups_.push_back(group); } + void setGroups(const std::vector<String>& groups) { groups_ = groups; } + const std::vector<String>& getGroups() const { return groups_; } + + void setSubscriptionRequested() { ask_ = true; } + bool getSubscriptionRequested() const { return ask_; } + + private: + JID jid_; + String name_; + Subscription subscription_; + std::vector<String> groups_; + bool ask_; + }; +} + +#endif diff --git a/Swiften/Elements/RosterPayload.cpp b/Swiften/Elements/RosterPayload.cpp new file mode 100644 index 0000000..6d39264 --- /dev/null +++ b/Swiften/Elements/RosterPayload.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +boost::optional<RosterItemPayload> RosterPayload::getItem(const JID& jid) const { + foreach(const RosterItemPayload& item, items_) { + // FIXME: MSVC rejects this. Find out why. + //if (item.getJID() == jid) { + if (item.getJID().compare(jid, JID::WithResource)) { + return boost::optional<RosterItemPayload>(item); + } + } + return boost::optional<RosterItemPayload>(); +} + +} diff --git a/Swiften/Elements/RosterPayload.h b/Swiften/Elements/RosterPayload.h new file mode 100644 index 0000000..afb68c2 --- /dev/null +++ b/Swiften/Elements/RosterPayload.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_RosterPayload_H +#define SWIFTEN_RosterPayload_H + +#include <vector> +#include <boost/optional.hpp> + +#include "Swiften/Elements/RosterItemPayload.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class RosterPayload : public Payload { + public: + typedef std::vector<RosterItemPayload> RosterItemPayloads; + + public: + RosterPayload() {} + + boost::optional<RosterItemPayload> getItem(const JID& jid) const; + + void addItem(const RosterItemPayload& item) { + items_.push_back(item); + } + + const RosterItemPayloads& getItems() const { + return items_; + } + + private: + RosterItemPayloads items_; + }; +} + +#endif diff --git a/Swiften/Elements/SecurityLabel.h b/Swiften/Elements/SecurityLabel.h new file mode 100644 index 0000000..65bdb4f --- /dev/null +++ b/Swiften/Elements/SecurityLabel.h @@ -0,0 +1,57 @@ +#ifndef SWIFTEN_SecurityLabel_H +#define SWIFTEN_SecurityLabel_H + +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class SecurityLabel : public Payload { + public: + SecurityLabel() {} + + const String& getDisplayMarking() const { return displayMarking_; } + + void setDisplayMarking(const String& displayMarking) { + displayMarking_ = displayMarking; + } + + const String& getForegroundColor() const { + return foregroundColor_; + } + + void setForegroundColor(const String& foregroundColor) { + foregroundColor_ = foregroundColor; + } + + const String& getBackgroundColor() const { + return backgroundColor_; + } + + void setBackgroundColor(const String& backgroundColor) { + backgroundColor_ = backgroundColor; + } + + const String& getLabel() const { return label_; } + + void setLabel(const String& label) { + label_ = label; + } + + const std::vector<String>& getEquivalentLabels() const { return equivalentLabels_; } + + void addEquivalentLabel(const String& label) { + equivalentLabels_.push_back(label); + } + + private: + String displayMarking_; + String foregroundColor_; + String backgroundColor_; + String label_; + std::vector<String> equivalentLabels_; + }; +} + +#endif diff --git a/Swiften/Elements/SecurityLabelsCatalog.h b/Swiften/Elements/SecurityLabelsCatalog.h new file mode 100644 index 0000000..611c26b --- /dev/null +++ b/Swiften/Elements/SecurityLabelsCatalog.h @@ -0,0 +1,56 @@ +#ifndef SWIFTEN_SecurityLabelsCatalog_H +#define SWIFTEN_SecurityLabelsCatalog_H + +#include <vector> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/SecurityLabel.h" + +namespace Swift { + class SecurityLabelsCatalog : public Payload { + public: + SecurityLabelsCatalog(const JID& to = JID()) : to_(to) {} + + const std::vector<SecurityLabel>& getLabels() const { + return labels_; + } + + void addLabel(const SecurityLabel& label) { + labels_.push_back(label); + } + + const JID& getTo() const { + return to_; + } + + void setTo(const JID& to) { + to_ = to; + } + + const String& getName() const { + return name_; + } + + void setName(const String& name) { + name_ = name; + } + + const String& getDescription() const { + return description_; + } + + void setDescription(const String& description) { + description_ = description; + } + + private: + JID to_; + String name_; + String description_; + std::vector<SecurityLabel> labels_; + }; +} + +#endif diff --git a/Swiften/Elements/SoftwareVersion.h b/Swiften/Elements/SoftwareVersion.h new file mode 100644 index 0000000..d064414 --- /dev/null +++ b/Swiften/Elements/SoftwareVersion.h @@ -0,0 +1,47 @@ +#ifndef SWIFTEN_SoftwareVersion_H +#define SWIFTEN_SoftwareVersion_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class SoftwareVersion : public Payload { + public: + SoftwareVersion( + const String& name = "", + const String& version = "", + const String& os = "") : + name_(name), version_(version), os_(os) {} + + const String& getName() const { + return name_; + } + + void setName(const String& name) { + name_ = name; + } + + const String& getVersion() const { + return version_; + } + + void setVersion(const String& version) { + version_ = version; + } + + const String& getOS() const { + return os_; + } + + void setOS(const String& os) { + os_ = os; + } + + private: + String name_; + String version_; + String os_; + }; +} + +#endif diff --git a/Swiften/Elements/Stanza.cpp b/Swiften/Elements/Stanza.cpp new file mode 100644 index 0000000..e644665 --- /dev/null +++ b/Swiften/Elements/Stanza.cpp @@ -0,0 +1,31 @@ +#include "Swiften/Elements/Stanza.h" + +#include <typeinfo> + +namespace Swift { + +Stanza::~Stanza() { + payloads_.clear(); +} + +void Stanza::updatePayload(boost::shared_ptr<Payload> payload) { + foreach (boost::shared_ptr<Payload>& i, payloads_) { + if (typeid(*i.get()) == typeid(*payload.get())) { + i = payload; + return; + } + } + addPayload(payload); +} + +boost::shared_ptr<Payload> Stanza::getPayloadOfSameType(boost::shared_ptr<Payload> payload) const { + foreach (const boost::shared_ptr<Payload>& i, payloads_) { + if (typeid(*i.get()) == typeid(*payload.get())) { + return i; + } + } + return boost::shared_ptr<Payload>(); +} + + +} diff --git a/Swiften/Elements/Stanza.h b/Swiften/Elements/Stanza.h new file mode 100644 index 0000000..e60ab21 --- /dev/null +++ b/Swiften/Elements/Stanza.h @@ -0,0 +1,60 @@ +#ifndef SWIFTEN_STANZAS_STANZA_H +#define SWIFTEN_STANZAS_STANZA_H + +#include <vector> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Element.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class Stanza : public Element { + public: + virtual ~Stanza(); + + template<typename T> + boost::shared_ptr<T> getPayload() const { + foreach (const boost::shared_ptr<Payload>& i, payloads_) { + boost::shared_ptr<T> result(boost::dynamic_pointer_cast<T>(i)); + if (result) { + return result; + } + } + return boost::shared_ptr<T>(); + } + + const std::vector< boost::shared_ptr<Payload> >& getPayloads() const { + return payloads_; + } + + void addPayload(boost::shared_ptr<Payload> payload) { + payloads_.push_back(payload); + } + + void updatePayload(boost::shared_ptr<Payload> payload); + + boost::shared_ptr<Payload> getPayloadOfSameType(boost::shared_ptr<Payload>) const; + + const JID& getFrom() const { return from_; } + void setFrom(const JID& from) { from_ = from; } + + const JID& getTo() const { return to_; } + void setTo(const JID& to) { to_ = to; } + + const String& getID() const { return id_; } + void setID(const String& id) { id_ = id; } + + private: + String id_; + JID from_; + JID to_; + + typedef std::vector< boost::shared_ptr<Payload> > Payloads; + Payloads payloads_; + }; +} + +#endif diff --git a/Swiften/Elements/StartSession.h b/Swiften/Elements/StartSession.h new file mode 100644 index 0000000..2b46d05 --- /dev/null +++ b/Swiften/Elements/StartSession.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartSession_H +#define SWIFTEN_StartSession_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class StartSession : public Payload { + public: + StartSession() {} + }; +} + +#endif diff --git a/Swiften/Elements/StartTLSFailure.h b/Swiften/Elements/StartTLSFailure.h new file mode 100644 index 0000000..17a1750 --- /dev/null +++ b/Swiften/Elements/StartTLSFailure.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_StartTLSFailure_H +#define SWIFTEN_StartTLSFailure_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StartTLSFailure : public Element { + public: + StartTLSFailure() {} + }; +} + +#endif diff --git a/Swiften/Elements/StartTLSRequest.h b/Swiften/Elements/StartTLSRequest.h new file mode 100644 index 0000000..c40499a --- /dev/null +++ b/Swiften/Elements/StartTLSRequest.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSRequest_H +#define SWIFTEN_StartTLSRequest_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StartTLSRequest : public Element + { + public: + StartTLSRequest() {} + }; +} + +#endif diff --git a/Swiften/Elements/Status.h b/Swiften/Elements/Status.h new file mode 100644 index 0000000..0b80682 --- /dev/null +++ b/Swiften/Elements/Status.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_Status_H +#define SWIFTEN_Status_H + +#include "Swiften/Elements/Payload.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Status : public Payload { + public: + Status(const String& text = "") : text_(text) { + } + + void setText(const String& text) { + text_ = text; + } + + const String& getText() const { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Elements/StatusShow.h b/Swiften/Elements/StatusShow.h new file mode 100644 index 0000000..a001657 --- /dev/null +++ b/Swiften/Elements/StatusShow.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_StatusShow_H +#define SWIFTEN_StatusShow_H + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class StatusShow : public Payload { + public: + enum Type { Online, Away, FFC, XA, DND, None }; + + StatusShow(const Type& type = Online) : type_(type) { + } + + void setType(const Type& type) { + type_ = type; + } + + const Type& getType() const { + return type_; + } + + private: + Type type_; + }; +} + +#endif diff --git a/Swiften/Elements/StreamFeatures.h b/Swiften/Elements/StreamFeatures.h new file mode 100644 index 0000000..2d5f4d6 --- /dev/null +++ b/Swiften/Elements/StreamFeatures.h @@ -0,0 +1,77 @@ +#ifndef SWIFTEN_StreamFeatures_H +#define SWIFTEN_StreamFeatures_H + +#include <vector> +#include <algorithm> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class StreamFeatures : public Element + { + public: + StreamFeatures() : hasStartTLS_(false), hasResourceBind_(false), hasSession_(false) {} + + void setHasStartTLS() { + hasStartTLS_ = true; + } + + bool hasStartTLS() const { + return hasStartTLS_; + } + + void setHasSession() { + hasSession_ = true; + } + + bool hasSession() const { + return hasSession_; + } + + void setHasResourceBind() { + hasResourceBind_ = true; + } + + bool hasResourceBind() const { + return hasResourceBind_; + } + + const std::vector<String>& getCompressionMethods() const { + return compressionMethods_; + } + + void addCompressionMethod(const String& mechanism) { + compressionMethods_.push_back(mechanism); + } + + bool hasCompressionMethod(const String& mechanism) const { + return std::find(compressionMethods_.begin(), compressionMethods_.end(), mechanism) != compressionMethods_.end(); + } + + const std::vector<String>& getAuthenticationMechanisms() const { + return authenticationMechanisms_; + } + + void addAuthenticationMechanism(const String& mechanism) { + authenticationMechanisms_.push_back(mechanism); + } + + bool hasAuthenticationMechanism(const String& mechanism) const { + return std::find(authenticationMechanisms_.begin(), authenticationMechanisms_.end(), mechanism) != authenticationMechanisms_.end(); + } + + bool hasAuthenticationMechanisms() const { + return !authenticationMechanisms_.empty(); + } + + private: + bool hasStartTLS_; + std::vector<String> compressionMethods_; + std::vector<String> authenticationMechanisms_; + bool hasResourceBind_; + bool hasSession_; + }; +} + +#endif diff --git a/Swiften/Elements/TLSProceed.h b/Swiften/Elements/TLSProceed.h new file mode 100644 index 0000000..41f0341 --- /dev/null +++ b/Swiften/Elements/TLSProceed.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_TLSProceed_H +#define SWIFTEN_TLSProceed_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class TLSProceed : public Element + { + public: + TLSProceed() {} + }; +} + +#endif diff --git a/Swiften/Elements/UnitTest/IQTest.cpp b/Swiften/Elements/UnitTest/IQTest.cpp new file mode 100644 index 0000000..bc22c81 --- /dev/null +++ b/Swiften/Elements/UnitTest/IQTest.cpp @@ -0,0 +1,52 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/SoftwareVersion.h" + +using namespace Swift; + +class IQTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IQTest); + CPPUNIT_TEST(testCreateResult); + CPPUNIT_TEST(testCreateResult_WithoutPayload); + CPPUNIT_TEST(testCreateError); + CPPUNIT_TEST_SUITE_END(); + + public: + IQTest() {} + + void testCreateResult() { + boost::shared_ptr<Payload> payload(new SoftwareVersion("myclient")); + boost::shared_ptr<IQ> iq(IQ::createResult(JID("foo@bar/fum"), "myid", payload)); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + CPPUNIT_ASSERT(iq->getPayload<SoftwareVersion>()); + CPPUNIT_ASSERT(payload == iq->getPayload<SoftwareVersion>()); + } + + void testCreateResult_WithoutPayload() { + boost::shared_ptr<IQ> iq(IQ::createResult(JID("foo@bar/fum"), "myid")); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + CPPUNIT_ASSERT(!iq->getPayload<SoftwareVersion>()); + } + + void testCreateError() { + boost::shared_ptr<IQ> iq(IQ::createError(JID("foo@bar/fum"), "myid", Error::BadRequest, Error::Modify)); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar/fum"), iq->getTo()); + CPPUNIT_ASSERT_EQUAL(String("myid"), iq->getID()); + boost::shared_ptr<Error> error(iq->getPayload<Error>()); + CPPUNIT_ASSERT(error); + CPPUNIT_ASSERT_EQUAL(Error::BadRequest, error->getCondition()); + CPPUNIT_ASSERT_EQUAL(Error::Modify, error->getType()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IQTest); diff --git a/Swiften/Elements/UnitTest/Makefile.inc b/Swiften/Elements/UnitTest/Makefile.inc new file mode 100644 index 0000000..848aa19 --- /dev/null +++ b/Swiften/Elements/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/Elements/UnitTest/StanzaTest.cpp \ + Swiften/Elements/UnitTest/IQTest.cpp diff --git a/Swiften/Elements/UnitTest/StanzaTest.cpp b/Swiften/Elements/UnitTest/StanzaTest.cpp new file mode 100644 index 0000000..b905957 --- /dev/null +++ b/Swiften/Elements/UnitTest/StanzaTest.cpp @@ -0,0 +1,155 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/Message.h" + +using namespace Swift; + +class StanzaTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StanzaTest); + CPPUNIT_TEST(testConstructor_Copy); + CPPUNIT_TEST(testGetPayload); + CPPUNIT_TEST(testGetPayload_NoSuchPayload); + CPPUNIT_TEST(testDestructor); + CPPUNIT_TEST(testDestructor_Copy); + CPPUNIT_TEST(testUpdatePayload_ExistingPayload); + CPPUNIT_TEST(testUpdatePayload_NewPayload); + CPPUNIT_TEST(testGetPayloadOfSameType); + CPPUNIT_TEST(testGetPayloadOfSameType_NoSuchPayload); + CPPUNIT_TEST_SUITE_END(); + + public: + class MyPayload1 : public Payload { + public: + MyPayload1() {} + }; + + class MyPayload2 : public Payload { + public: + MyPayload2(const String& s = "") : text_(s) {} + + String text_; + }; + + class MyPayload3 : public Payload { + public: + MyPayload3() {} + }; + + class DestroyingPayload : public Payload { + public: + DestroyingPayload(bool* alive) : alive_(alive) { + } + + ~DestroyingPayload() { + (*alive_) = false; + } + + private: + bool* alive_; + }; + + StanzaTest() {} + + void testConstructor_Copy() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2())); + Message copy(m); + + CPPUNIT_ASSERT(copy.getPayload<MyPayload1>()); + CPPUNIT_ASSERT(copy.getPayload<MyPayload2>()); + } + + void testDestructor() { + bool payloadAlive = true; + { + Message m; + m.addPayload(boost::shared_ptr<DestroyingPayload>(new DestroyingPayload(&payloadAlive))); + } + + CPPUNIT_ASSERT(!payloadAlive); + } + + void testDestructor_Copy() { + bool payloadAlive = true; + Message* m1 = new Message(); + m1->addPayload(boost::shared_ptr<DestroyingPayload>(new DestroyingPayload(&payloadAlive))); + Message* m2 = new Message(*m1); + + delete m1; + CPPUNIT_ASSERT(payloadAlive); + + delete m2; + CPPUNIT_ASSERT(!payloadAlive); + } + + void testGetPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT(p); + } + + void testGetPayload_NoSuchPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT(!p); + } + + void testUpdatePayload_ExistingPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2("foo"))); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + m.updatePayload(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), m.getPayloads().size()); + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT_EQUAL(String("bar"), p->text_); + } + + void testUpdatePayload_NewPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + m.updatePayload(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), m.getPayloads().size()); + boost::shared_ptr<MyPayload2> p(m.getPayload<MyPayload2>()); + CPPUNIT_ASSERT_EQUAL(String("bar"), p->text_); + } + + void testGetPayloadOfSameType() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload2>(new MyPayload2("foo"))); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + boost::shared_ptr<MyPayload2> payload(boost::dynamic_pointer_cast<MyPayload2>(m.getPayloadOfSameType(boost::shared_ptr<MyPayload2>(new MyPayload2("bar"))))); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("foo"), payload->text_); + } + + void testGetPayloadOfSameType_NoSuchPayload() { + Message m; + m.addPayload(boost::shared_ptr<MyPayload1>(new MyPayload1())); + m.addPayload(boost::shared_ptr<MyPayload3>(new MyPayload3())); + + CPPUNIT_ASSERT(!m.getPayloadOfSameType(boost::shared_ptr<MyPayload2>(new MyPayload2("bar")))); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StanzaTest); diff --git a/Swiften/Elements/UnitTest/StanzasTest.cpp b/Swiften/Elements/UnitTest/StanzasTest.cpp new file mode 100644 index 0000000..35b84e7 --- /dev/null +++ b/Swiften/Elements/UnitTest/StanzasTest.cpp @@ -0,0 +1,3 @@ +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Presence.h" diff --git a/Swiften/Elements/UnknownElement.h b/Swiften/Elements/UnknownElement.h new file mode 100644 index 0000000..a2ae406 --- /dev/null +++ b/Swiften/Elements/UnknownElement.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_UnknownElement_H +#define SWIFTEN_UnknownElement_H + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class UnknownElement : public Element + { + public: + UnknownElement() {} + }; +} + +#endif diff --git a/Swiften/Elements/Version.h b/Swiften/Elements/Version.h new file mode 100644 index 0000000..327178e --- /dev/null +++ b/Swiften/Elements/Version.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_STANZAS_VERSION_H +#define SWIFTEN_STANZAS_VERSION_H + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class Version : public Payload + { + public: + Version(const String& name = "", const String& version = "", const String& os = "") : name_(name), version_(version), os_(os) { } + + const String& getName() const { return name_; } + const String& getVersion() const { return version_; } + const String& getOS() const { return os_; } + + private: + String name_; + String version_; + String os_; + }; +} + +#endif diff --git a/Swiften/EventLoop/Deleter.h b/Swiften/EventLoop/Deleter.h new file mode 100644 index 0000000..217a17f --- /dev/null +++ b/Swiften/EventLoop/Deleter.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_Deleter_H +#define SWIFTEN_Deleter_H + +#include <cassert> + +namespace Swift { + template<typename T> + class Deleter { + public: + Deleter(T* object) : object_(object) { + } + + void operator()() { + assert(object_); + delete object_; + object_ = 0; + } + + private: + T* object_; + }; +} +#endif diff --git a/Swiften/EventLoop/DummyEventLoop.h b/Swiften/EventLoop/DummyEventLoop.h new file mode 100644 index 0000000..234ecfa --- /dev/null +++ b/Swiften/EventLoop/DummyEventLoop.h @@ -0,0 +1,37 @@ +#ifndef SWIFTEN_DummyEventLoop_H +#define SWIFTEN_DummyEventLoop_H + +#include <deque> +#include <boost/function.hpp> + +#include "Swiften/EventLoop/EventLoop.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + class DummyEventLoop : public EventLoop { + public: + DummyEventLoop() { + } + + void processEvents() { + while (!events_.empty()) { + handleEvent(events_[0]); + events_.pop_front(); + } + } + + bool hasEvents() { + return events_.size() > 0; + } + + virtual void post(const Event& event) { + events_.push_back(event); + } + + private: + std::deque<Event> events_; + }; +} + +#endif + diff --git a/Swiften/EventLoop/EventLoop.cpp b/Swiften/EventLoop/EventLoop.cpp new file mode 100644 index 0000000..cec149c --- /dev/null +++ b/Swiften/EventLoop/EventLoop.cpp @@ -0,0 +1,49 @@ +#include "Swiften/EventLoop/EventLoop.h" + +#include <algorithm> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +EventLoop::EventLoop() : nextEventID_(0) { + MainEventLoop::setInstance(this); +} + +EventLoop::~EventLoop() { + MainEventLoop::resetInstance(); +} + +void EventLoop::handleEvent(const Event& event) { + bool doCallback = false; + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + std::list<Event>::iterator i = std::find(events_.begin(), events_.end(), event); + if (i != events_.end()) { + doCallback = true; + events_.erase(i); + } + } + if (doCallback) { + event.callback(); + } +} + +void EventLoop::postEvent(boost::function<void ()> callback, void* owner) { + Event event(owner, callback); + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + event.id = nextEventID_; + nextEventID_++; + events_.push_back(event); + } + post(event); +} + +void EventLoop::removeEventsFromOwner(void* owner) { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + events_.remove_if(HasOwner(owner)); +} + +} diff --git a/Swiften/EventLoop/EventLoop.h b/Swiften/EventLoop/EventLoop.h new file mode 100644 index 0000000..2f04f32 --- /dev/null +++ b/Swiften/EventLoop/EventLoop.h @@ -0,0 +1,52 @@ +#ifndef SWIFTEN_EventLoop_H +#define SWIFTEN_EventLoop_H + +#include <boost/function.hpp> +#include <boost/thread/mutex.hpp> +#include <list> + +namespace Swift { + class EventLoop { + public: + EventLoop(); + virtual ~EventLoop(); + + void postEvent(boost::function<void ()> event, void* owner); + void removeEventsFromOwner(void* owner); + + protected: + struct Event { + Event(void* owner, const boost::function<void()>& callback) : + owner(owner), callback(callback) { + } + + bool operator==(const Event& o) const { + return o.id == id; + } + + unsigned int id; + void* owner; + boost::function<void()> callback; + }; + + /** + * Reimplement this to call handleEvent(event) from the thread in which + * the event loop is residing. + */ + virtual void post(const Event& event) = 0; + + void handleEvent(const Event& event); + + private: + struct HasOwner { + HasOwner(void* owner) : owner(owner) {} + bool operator()(const Event& event) { return event.owner == owner; } + void* owner; + }; + boost::mutex eventsMutex_; + unsigned int nextEventID_; + std::list<Event> events_; + }; +} + +#endif diff --git a/Swiften/EventLoop/MainEventLoop.cpp b/Swiften/EventLoop/MainEventLoop.cpp new file mode 100644 index 0000000..afaab42 --- /dev/null +++ b/Swiften/EventLoop/MainEventLoop.cpp @@ -0,0 +1,35 @@ +#include "Swiften/EventLoop/MainEventLoop.h" + +#include <iostream> + +namespace Swift { + +EventLoop* MainEventLoop::getInstance() { + if (!instance_) { + std::cerr << "No main event loop instantiated. Please instantiate the appropriate subclass of EventLoop (e.g. SimpleEventLoop, QtEventLoop) at the start of your application." << std::endl; + exit(-1); + } + return instance_; +} + +void MainEventLoop::setInstance(EventLoop* loop) { + assert(!instance_); + instance_ = loop; +} + +void MainEventLoop::resetInstance() { + assert(instance_); + instance_ = 0; +} + +void MainEventLoop::postEvent(boost::function<void ()> event, void* owner) { + getInstance()->postEvent(event, owner); +} + +void MainEventLoop::removeEventsFromOwner(void* owner) { + getInstance()->removeEventsFromOwner(owner); +} + +EventLoop* MainEventLoop::instance_ = 0; + +} diff --git a/Swiften/EventLoop/MainEventLoop.h b/Swiften/EventLoop/MainEventLoop.h new file mode 100644 index 0000000..f29dbd4 --- /dev/null +++ b/Swiften/EventLoop/MainEventLoop.h @@ -0,0 +1,40 @@ +#ifndef SWIFTEN_MainEventLoop_H +#define SWIFTEN_MainEventLoop_H + +#include <boost/function.hpp> + +#include "Swiften/EventLoop/Deleter.h" +#include "Swiften/EventLoop/EventLoop.h" + +namespace Swift { + class EventLoop; + + class MainEventLoop { + friend class EventLoop; + + public: + /** + * Post an event from the given owner to the event loop. + * If the owner is destroyed, all events should be removed from the + * loop using removeEventsFromOwner(). + */ + static void postEvent(boost::function<void ()> event, void* owner = 0); + + static void removeEventsFromOwner(void* owner); + + template<typename T> + static void deleteLater(T* t) { + getInstance()->postEvent(Deleter<T>(t), 0); + } + + private: + static void setInstance(EventLoop*); + static void resetInstance(); + static EventLoop* getInstance(); + + private: + static EventLoop* instance_; + }; +} + +#endif diff --git a/Swiften/EventLoop/Makefile.inc b/Swiften/EventLoop/Makefile.inc new file mode 100644 index 0000000..894b18d --- /dev/null +++ b/Swiften/EventLoop/Makefile.inc @@ -0,0 +1,6 @@ +SWIFTEN_SOURCES += \ + Swiften/EventLoop/EventLoop.cpp \ + Swiften/EventLoop/SimpleEventLoop.cpp \ + Swiften/EventLoop/MainEventLoop.cpp + +include Swiften/EventLoop/UnitTest/Makefile.inc diff --git a/Swiften/EventLoop/SimpleEventLoop.cpp b/Swiften/EventLoop/SimpleEventLoop.cpp new file mode 100644 index 0000000..96ad774 --- /dev/null +++ b/Swiften/EventLoop/SimpleEventLoop.cpp @@ -0,0 +1,48 @@ +#include "Swiften/EventLoop/SimpleEventLoop.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" + + +namespace Swift { + +void nop() {} + +SimpleEventLoop::SimpleEventLoop() : isRunning_(true) { +} + +void SimpleEventLoop::run() { + while (isRunning_) { + std::vector<Event> events; + { + boost::unique_lock<boost::mutex> lock(eventsMutex_); + while (events_.size() == 0) { + eventsAvailable_.wait(lock); + } + events.swap(events_); + } + foreach(const Event& event, events) { + handleEvent(event); + } + } +} + +void SimpleEventLoop::stop() { + postEvent(boost::bind(&SimpleEventLoop::doStop, this), 0); +} + +void SimpleEventLoop::doStop() { + isRunning_ = false; +} + +void SimpleEventLoop::post(const Event& event) { + { + boost::lock_guard<boost::mutex> lock(eventsMutex_); + events_.push_back(event); + } + eventsAvailable_.notify_one(); +} + + +} diff --git a/Swiften/EventLoop/SimpleEventLoop.h b/Swiften/EventLoop/SimpleEventLoop.h new file mode 100644 index 0000000..45eaae1 --- /dev/null +++ b/Swiften/EventLoop/SimpleEventLoop.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_SimpleEventLoop_H +#define SWIFTEN_SimpleEventLoop_H + +#include <vector> +#include <boost/function.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +#include "Swiften/EventLoop/EventLoop.h" + +namespace Swift { + class SimpleEventLoop : public EventLoop { + public: + SimpleEventLoop(); + + void run(); + void stop(); + + virtual void post(const Event& event); + + private: + void doStop(); + + private: + bool isRunning_; + std::vector<Event> events_; + boost::mutex eventsMutex_; + boost::condition_variable eventsAvailable_; + }; +} +#endif diff --git a/Swiften/EventLoop/UnitTest/EventLoopTest.cpp b/Swiften/EventLoop/UnitTest/EventLoopTest.cpp new file mode 100644 index 0000000..c64d1ad --- /dev/null +++ b/Swiften/EventLoop/UnitTest/EventLoopTest.cpp @@ -0,0 +1,63 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/thread.hpp> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Base/sleep.h" + +using namespace Swift; + +class EventLoopTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(EventLoopTest); + CPPUNIT_TEST(testPost); + CPPUNIT_TEST(testRemove); + CPPUNIT_TEST_SUITE_END(); + + public: + EventLoopTest() {} + + void setUp() { + events_.clear(); + } + + void testPost() { + SimpleEventLoop testling; + + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 1), 0); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 2), 0); + testling.stop(); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(events_.size())); + CPPUNIT_ASSERT_EQUAL(1, events_[0]); + CPPUNIT_ASSERT_EQUAL(2, events_[1]); + } + + void testRemove() { + SimpleEventLoop testling; + + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 1), &testling); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 2), this); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 3), &testling); + testling.postEvent(boost::bind(&EventLoopTest::logEvent, this, 4), this); + testling.removeEventsFromOwner(this); + testling.stop(); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(events_.size())); + CPPUNIT_ASSERT_EQUAL(1, events_[0]); + CPPUNIT_ASSERT_EQUAL(3, events_[1]); + } + + private: + void logEvent(int i) { + events_.push_back(i); + } + + private: + std::vector<int> events_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(EventLoopTest); diff --git a/Swiften/EventLoop/UnitTest/Makefile.inc b/Swiften/EventLoop/UnitTest/Makefile.inc new file mode 100644 index 0000000..5eec2da --- /dev/null +++ b/Swiften/EventLoop/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp \ + Swiften/EventLoop/UnitTest/EventLoopTest.cpp diff --git a/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp b/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp new file mode 100644 index 0000000..a39aa9a --- /dev/null +++ b/Swiften/EventLoop/UnitTest/SimpleEventLoopTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/thread.hpp> +#include <boost/bind.hpp> + +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Base/sleep.h" + +using namespace Swift; + +class SimpleEventLoopTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SimpleEventLoopTest); + CPPUNIT_TEST(testRun); + CPPUNIT_TEST(testPostFromMainThread); + CPPUNIT_TEST_SUITE_END(); + + public: + SimpleEventLoopTest() {} + + void setUp() { + counter_ = 0; + } + + void testRun() { + SimpleEventLoop testling; + boost::thread thread(boost::bind(&SimpleEventLoopTest::runIncrementingThread, this, &testling)); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(10, counter_); + } + + void testPostFromMainThread() { + SimpleEventLoop testling; + testling.postEvent(boost::bind(&SimpleEventLoopTest::incrementCounterAndStop, this, &testling), 0); + testling.run(); + + CPPUNIT_ASSERT_EQUAL(1, counter_); + } + + private: + void runIncrementingThread(SimpleEventLoop* loop) { + for (unsigned int i = 0; i < 10; ++i) { + Swift::sleep(1); + loop->postEvent(boost::bind(&SimpleEventLoopTest::incrementCounter, this), 0); + } + loop->stop(); + } + + void incrementCounter() { + counter_++; + } + + void incrementCounterAndStop(SimpleEventLoop* loop) { + counter_++; + loop->stop(); + } + + int counter_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SimpleEventLoopTest); diff --git a/Swiften/Events/Makefile.inc b/Swiften/Events/Makefile.inc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Swiften/Events/Makefile.inc diff --git a/Swiften/Events/MessageEvent.h b/Swiften/Events/MessageEvent.h new file mode 100644 index 0000000..27eecaf --- /dev/null +++ b/Swiften/Events/MessageEvent.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_MessageEvent_H +#define SWIFTEN_MessageEvent_H + +#include <cassert> + +#include "Swiften/Elements/Message.h" + +#include <boost/shared_ptr.hpp> + +namespace Swift { + class MessageEvent { + public: + MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza){} + boost::shared_ptr<Message> getStanza() {return stanza_;} + boost::signal<void()> onRead; + + bool isReadable() { + return getStanza()->isError() || !getStanza()->getBody().isEmpty(); + } + + void read() { + assert (isReadable()); + onRead(); + } + + private: + boost::shared_ptr<Message> stanza_; + }; +} + +#endif diff --git a/Swiften/Examples/Makefile.inc b/Swiften/Examples/Makefile.inc new file mode 100644 index 0000000..4f61f2c --- /dev/null +++ b/Swiften/Examples/Makefile.inc @@ -0,0 +1,4 @@ +include Swiften/Examples/TuneBot/Makefile.inc + +.PHONY: examples +examples: $(EXAMPLES_TARGETS) diff --git a/Swiften/Examples/TuneBot/Makefile.inc b/Swiften/Examples/TuneBot/Makefile.inc new file mode 100644 index 0000000..c7c00c7 --- /dev/null +++ b/Swiften/Examples/TuneBot/Makefile.inc @@ -0,0 +1,11 @@ +TUNEBOT_TARGET = Swiften/Examples/TuneBot/TuneBot +TUNEBOT_SOURCES += \ + Swiften/Examples/TuneBot/TuneBot.cpp +TUNEBOT_OBJECTS = \ + $(TUNEBOT_SOURCES:.cpp=.o) + +CLEANFILES += $(TUNEBOT_OBJECTS) $(TUNEBOT_TARGET) +EXAMPLES_TARGETS += $(TUNEBOT_TARGET) + +$(TUNEBOT_TARGET): $(SWIFTEN_TARGET) $(TUNEBOT_OBJECTS) + $(QUIET_LINK)$(CXX) -o $(TUNEBOT_TARGET) $(TUNEBOT_OBJECTS) $(LDFLAGS) $(CPPCLIENT_LDFLAGS) $(SWIFTEN_TARGET) $(LIBS) diff --git a/Swiften/Examples/TuneBot/TuneBot.cpp b/Swiften/Examples/TuneBot/TuneBot.cpp new file mode 100644 index 0000000..d2bb100 --- /dev/null +++ b/Swiften/Examples/TuneBot/TuneBot.cpp @@ -0,0 +1,72 @@ +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Elements/CapsInfo.h" +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Disco/CapsInfoGenerator.h" + +using namespace Swift; +using namespace boost; + +class TuneBot { + public: + TuneBot(const JID& jid, const String& password) { + client_ = new Client(jid, password); + router_ = new IQRouter(client_); + + DiscoInfo discoInfo; + discoInfo.addIdentity(DiscoInfo::Identity("TuneBot", "client", "bot")); + discoInfo.addFeature("http://jabber.org/protocol/tune+notify"); + capsInfo_ = boost::shared_ptr<CapsInfo>(new CapsInfo(CapsInfoGenerator("http://el-tramo.be/tunebot").generateCapsInfo(discoInfo))); + discoResponder_ = new DiscoInfoResponder(router_); + discoResponder_->setDiscoInfo(discoInfo); + discoResponder_->setDiscoInfo(capsInfo_->getNode() + "#" + capsInfo_->getVersion(), discoInfo); + + client_->onSessionStarted.connect(bind(&TuneBot::handleSessionStarted, this)); + client_->onMessageReceived.connect(bind(&TuneBot::handleMessage, this, _1)); + client_->connect(); + } + + void handleSessionStarted() { + GetRosterRequest* rosterRequest = new GetRosterRequest(router_, Request::AutoDeleteAfterResponse); + rosterRequest->onResponse.connect(bind(&TuneBot::handleRosterReceived, this, _1)); + rosterRequest->send(); + } + + void handleRosterReceived(shared_ptr<Payload>) { + boost::shared_ptr<Presence> presence(new Presence()); + presence->addPayload(capsInfo_); + presence->setPriority(-1); + client_->send(presence); + } + + void handleMessage(shared_ptr<Message> message) { + // TODO + } + + private: + Client* client_; + IQRouter* router_; + DiscoInfoResponder* discoResponder_; + boost::shared_ptr<CapsInfo> capsInfo_; +}; + + +int main(int argc, char* argv[]) +{ + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " <jid> <password>" << std::endl; + return -1; + } + + SimpleEventLoop eventLoop; + + TuneBot bot(argv[1], argv[2]); + eventLoop.run(); +} diff --git a/Swiften/JID/JID.cpp b/Swiften/JID/JID.cpp new file mode 100644 index 0000000..dcd6dd1 --- /dev/null +++ b/Swiften/JID/JID.cpp @@ -0,0 +1,116 @@ +#include <stringprep.h> +#include <vector> +#include <iostream> + +#include "Swiften/JID/JID.h" + +namespace Swift { + + +class StringPrepper { + private: + static const int MAX_STRINGPREP_SIZE = 1024; + + public: + static String getNamePrepped(const String& name) { + return getStringPrepped(name, stringprep_nameprep); + } + + static String getNodePrepped(const String& node) { + return getStringPrepped(node, stringprep_xmpp_nodeprep); + } + + static String getResourcePrepped(const String& resource) { + return getStringPrepped(resource, stringprep_xmpp_resourceprep); + } + + static String getStringPrepped(const String& s, const Stringprep_profile profile[]) { + std::vector<char> input(s.getUTF8String().begin(), s.getUTF8String().end()); + input.resize(MAX_STRINGPREP_SIZE); + if (stringprep(&input[0], MAX_STRINGPREP_SIZE, static_cast<Stringprep_profile_flags>(0), profile) == 0) { + return String(&input[0]); + } + else { + return ""; + } + } +}; + + +JID::JID(const char* jid) { + initializeFromString(String(jid)); +} + +JID::JID(const String& jid) { + initializeFromString(jid); +} + +JID::JID(const String& node, const String& domain) : hasResource_(false) { + nameprepAndSetComponents(node, domain, ""); +} + +JID::JID(const String& node, const String& domain, const String& resource) : hasResource_(true) { + nameprepAndSetComponents(node, domain, resource); +} + +void JID::initializeFromString(const String& jid) { + if (jid.beginsWith('@')) { + return; + } + + String bare, resource; + size_t slashIndex = jid.find('/'); + if (slashIndex != jid.npos()) { + hasResource_ = true; + bare = jid.getSubstring(0, slashIndex); + resource = jid.getSubstring(slashIndex + 1, jid.npos()); + } + else { + hasResource_ = false; + bare = jid; + } + std::pair<String,String> nodeAndDomain = bare.getSplittedAtFirst('@'); + if (nodeAndDomain.second.isEmpty()) { + nameprepAndSetComponents("", nodeAndDomain.first, resource); + } + else { + nameprepAndSetComponents(nodeAndDomain.first, nodeAndDomain.second, resource); + } +} + + +void JID::nameprepAndSetComponents(const String& node, const String& domain, const String& resource) { + node_ = StringPrepper::getNamePrepped(node); + domain_ = StringPrepper::getNodePrepped(domain); + resource_ = StringPrepper::getResourcePrepped(resource); +} + +String JID::toString() const { + String string; + if (!node_.isEmpty()) { + string += node_ + "@"; + } + string += domain_; + if (!isBare()) { + string += "/" + resource_; + } + return string; +} + +int JID::compare(const Swift::JID& o, CompareType compareType) const { + if (node_ < o.node_) { return -1; } + if (node_ > o.node_) { return 1; } + if (domain_ < o.domain_) { return -1; } + if (domain_ > o.domain_) { return 1; } + if (compareType == WithResource) { + if (hasResource_ != o.hasResource_) { + return hasResource_ ? 1 : -1; + } + if (resource_ < o.resource_) { return -1; } + if (resource_ > o.resource_) { return 1; } + } + return 0; +} + +} // namespace Swift + diff --git a/Swiften/JID/JID.h b/Swiften/JID/JID.h new file mode 100644 index 0000000..ad89c85 --- /dev/null +++ b/Swiften/JID/JID.h @@ -0,0 +1,68 @@ +#ifndef SWIFTEN_JID_H +#define SWIFTEN_JID_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class JID + { + public: + enum CompareType { WithResource, WithoutResource }; + + explicit JID(const String& = String()); + explicit JID(const char*); + JID(const String& node, const String& domain); + JID(const String& node, const String& domain, const String& resource); + + bool isValid() const { return !domain_.isEmpty(); /* FIXME */ } + + const String& getNode() const { return node_; } + const String& getDomain() const { return domain_; } + const String& getResource() const { return resource_; } + bool isBare() const { return !hasResource_; } + + JID toBare() const { return JID(getNode(), getDomain()); /* FIXME: Duplicate unnecessary nameprepping. Probably ok. */ } + + String toString() const; + + bool equals(const JID& o, CompareType compareType) const { + return compare(o, compareType) == 0; + } + + int compare(const JID& o, CompareType compareType) const; + + operator String () const { return toString(); } + + bool operator<(const Swift::JID& b) const { + return compare(b, Swift::JID::WithResource) < 0; + } + + friend std::ostream& operator<<(std::ostream& os, const Swift::JID& j) { + os << j.toString(); + return os; + } + + friend bool operator==(const Swift::JID& a, const Swift::JID& b) { + return a.compare(b, Swift::JID::WithResource) == 0; + } + + friend bool operator!=(const Swift::JID& a, const Swift::JID& b) { + return a.compare(b, Swift::JID::WithResource) != 0; + } + + protected: + void nameprepAndSetComponents(const String& node, const String& domain, const String& resource); + + private: + void initializeFromString(const String&); + + private: + String node_; + String domain_; + bool hasResource_; + String resource_; + }; +} + +#endif + diff --git a/Swiften/JID/Makefile.inc b/Swiften/JID/Makefile.inc new file mode 100644 index 0000000..f60cb3c --- /dev/null +++ b/Swiften/JID/Makefile.inc @@ -0,0 +1,4 @@ +SWIFTEN_SOURCES += \ + Swiften/JID/JID.cpp + +include Swiften/JID/UnitTest/Makefile.inc diff --git a/Swiften/JID/UnitTest/JIDTest.cpp b/Swiften/JID/UnitTest/JIDTest.cpp new file mode 100644 index 0000000..917f89f --- /dev/null +++ b/Swiften/JID/UnitTest/JIDTest.cpp @@ -0,0 +1,310 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/JID/JID.h" + +using namespace Swift; + +class JIDTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(JIDTest); + CPPUNIT_TEST(testConstructorWithString); + CPPUNIT_TEST(testConstructorWithString_NoResource); + CPPUNIT_TEST(testConstructorWithString_NoNode); + CPPUNIT_TEST(testConstructorWithString_EmptyResource); + CPPUNIT_TEST(testConstructorWithString_OnlyDomain); + CPPUNIT_TEST(testConstructorWithString_UpperCaseNode); + CPPUNIT_TEST(testConstructorWithString_UpperCaseDomain); + CPPUNIT_TEST(testConstructorWithString_UpperCaseResource); + CPPUNIT_TEST(testConstructorWithString_EmptyNode); + CPPUNIT_TEST(testConstructorWithStrings); + CPPUNIT_TEST(testIsBare); + CPPUNIT_TEST(testIsBare_NotBare); + CPPUNIT_TEST(testToBare); + CPPUNIT_TEST(testToBare_EmptyNode); + CPPUNIT_TEST(testToBare_EmptyResource); + CPPUNIT_TEST(testToString); + CPPUNIT_TEST(testToString_EmptyNode); + CPPUNIT_TEST(testToString_EmptyResource); + CPPUNIT_TEST(testToString_NoResource); + CPPUNIT_TEST(testCompare_SmallerNode); + CPPUNIT_TEST(testCompare_LargerNode); + CPPUNIT_TEST(testCompare_SmallerDomain); + CPPUNIT_TEST(testCompare_LargerDomain); + CPPUNIT_TEST(testCompare_SmallerResource); + CPPUNIT_TEST(testCompare_LargerResource); + CPPUNIT_TEST(testCompare_Equal); + CPPUNIT_TEST(testCompare_EqualWithoutResource); + CPPUNIT_TEST(testCompare_NoResourceAndEmptyResource); + CPPUNIT_TEST(testCompare_EmptyResourceAndNoResource); + CPPUNIT_TEST(testEquals); + CPPUNIT_TEST(testEquals_NotEqual); + CPPUNIT_TEST(testEquals_WithoutResource); + CPPUNIT_TEST(testSmallerThan); + CPPUNIT_TEST(testSmallerThan_Equal); + CPPUNIT_TEST(testSmallerThan_Larger); + CPPUNIT_TEST(testHasResource); + CPPUNIT_TEST(testHasResource_NoResource); + CPPUNIT_TEST_SUITE_END(); + + public: + JIDTest() {} + + void testConstructorWithString() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_NoResource() { + JID testling("foo@bar"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResource()); + CPPUNIT_ASSERT(testling.isBare()); + } + + void testConstructorWithString_EmptyResource() { + JID testling("bar/"); + + CPPUNIT_ASSERT(testling.isValid()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_NoNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testConstructorWithString_OnlyDomain() { + JID testling("bar"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResource()); + CPPUNIT_ASSERT(testling.isBare()); + } + + void testConstructorWithString_UpperCaseNode() { + JID testling("Fo\xCE\xA9@bar"); + + CPPUNIT_ASSERT_EQUAL(String("fo\xCF\x89"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + } + + void testConstructorWithString_UpperCaseDomain() { + JID testling("Fo\xCE\xA9"); + + CPPUNIT_ASSERT_EQUAL(String("fo\xCF\x89"), testling.getDomain()); + } + + void testConstructorWithString_UpperCaseResource() { + JID testling("bar/Fo\xCE\xA9"); + + CPPUNIT_ASSERT_EQUAL(testling.getResource(), String("Fo\xCE\xA9")); + } + + void testConstructorWithString_EmptyNode() { + JID testling("@bar"); + + CPPUNIT_ASSERT(!testling.isValid()); + } + + void testConstructorWithStrings() { + JID testling("foo", "bar", "baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.getDomain()); + CPPUNIT_ASSERT_EQUAL(String("baz"), testling.getResource()); + } + + void testIsBare() { + CPPUNIT_ASSERT(JID("foo@bar").isBare()); + } + + void testIsBare_NotBare() { + CPPUNIT_ASSERT(!JID("foo@bar/baz").isBare()); + } + + void testToBare() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo"), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToBare_EmptyNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToBare_EmptyResource() { + JID testling("bar/"); + + CPPUNIT_ASSERT_EQUAL(String(""), testling.toBare().getNode()); + CPPUNIT_ASSERT_EQUAL(String("bar"), testling.toBare().getDomain()); + CPPUNIT_ASSERT(testling.toBare().isBare()); + } + + void testToString() { + JID testling("foo@bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar/baz"), testling.toString()); + } + + void testToString_EmptyNode() { + JID testling("bar/baz"); + + CPPUNIT_ASSERT_EQUAL(String("bar/baz"), testling.toString()); + } + + void testToString_NoResource() { + JID testling("foo@bar"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar"), testling.toString()); + } + + void testToString_EmptyResource() { + JID testling("foo@bar/"); + + CPPUNIT_ASSERT_EQUAL(String("foo@bar/"), testling.toString()); + } + + void testCompare_SmallerNode() { + JID testling1("a@c"); + JID testling2("b@b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerNode() { + JID testling1("c@a"); + JID testling2("b@b"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_SmallerDomain() { + JID testling1("x@a/c"); + JID testling2("x@b/b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerDomain() { + JID testling1("x@b/b"); + JID testling2("x@a/c"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_SmallerResource() { + JID testling1("x@y/a"); + JID testling2("x@y/b"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_LargerResource() { + JID testling1("x@y/b"); + JID testling2("x@y/a"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_Equal() { + JID testling1("x@y/z"); + JID testling2("x@y/z"); + + CPPUNIT_ASSERT_EQUAL(0, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_EqualWithoutResource() { + JID testling1("x@y/a"); + JID testling2("x@y/b"); + + CPPUNIT_ASSERT_EQUAL(0, testling1.compare(testling2, JID::WithoutResource)); + } + + void testCompare_NoResourceAndEmptyResource() { + JID testling1("x@y/"); + JID testling2("x@y"); + + CPPUNIT_ASSERT_EQUAL(1, testling1.compare(testling2, JID::WithResource)); + } + + void testCompare_EmptyResourceAndNoResource() { + JID testling1("x@y"); + JID testling2("x@y/"); + + CPPUNIT_ASSERT_EQUAL(-1, testling1.compare(testling2, JID::WithResource)); + } + + void testEquals() { + JID testling1("x@y/c"); + JID testling2("x@y/c"); + + CPPUNIT_ASSERT(testling1.equals(testling2, JID::WithResource)); + } + + void testEquals_NotEqual() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(!testling1.equals(testling2, JID::WithResource)); + } + + void testEquals_WithoutResource() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(testling1.equals(testling2, JID::WithoutResource)); + } + + void testSmallerThan() { + JID testling1("x@y/c"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(testling1 < testling2); + } + + void testSmallerThan_Equal() { + JID testling1("x@y/d"); + JID testling2("x@y/d"); + + CPPUNIT_ASSERT(!(testling1 < testling2)); + } + + void testSmallerThan_Larger() { + JID testling1("x@y/d"); + JID testling2("x@y/c"); + + CPPUNIT_ASSERT(!(testling1 < testling2)); + } + + void testHasResource() { + JID testling("x@y/d"); + + CPPUNIT_ASSERT(!testling.isBare()); + } + + void testHasResource_NoResource() { + JID testling("x@y"); + + CPPUNIT_ASSERT(testling.isBare()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(JIDTest); diff --git a/Swiften/JID/UnitTest/Makefile.inc b/Swiften/JID/UnitTest/Makefile.inc new file mode 100644 index 0000000..e896fd7 --- /dev/null +++ b/Swiften/JID/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/JID/UnitTest/JIDTest.cpp diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp new file mode 100644 index 0000000..2b8054f --- /dev/null +++ b/Swiften/MUC/MUC.cpp @@ -0,0 +1,69 @@ +#include "Swiften/MUC/MUC.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Client/StanzaChannel.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/MUCPayload.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + +typedef std::pair<String, MUCOccupant> StringMUCOccupantPair; + +MUC::MUC(StanzaChannel* stanzaChannel, const JID &muc) : muc_(muc), stanzaChannel_(stanzaChannel) { + stanzaChannel_->onPresenceReceived.connect(boost::bind(&MUC::handleIncomingPresence, this, _1)); +} + +MUC::~MUC() { +} + +void MUC::joinAs(const String &nick) { + boost::shared_ptr<Presence> joinPresence(new Presence()); + joinPresence->setTo(JID(muc_.getNode(), muc_.getDomain(), nick)); + joinPresence->addPayload(boost::shared_ptr<Payload>(new MUCPayload())); + stanzaChannel_->sendPresence(joinPresence); + myNick_ = nick; +} + +void MUC::part() { + boost::shared_ptr<Presence> partPresence(new Presence()); + partPresence->setType(Presence::Unavailable); + partPresence->setTo(JID(muc_.getNode(), muc_.getDomain(), myNick_)); + stanzaChannel_->sendPresence(partPresence); +} + +void MUC::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + if (presence->getFrom().toBare() != muc_ || presence->getFrom().getResource() == "") { + return; + } + String nick = presence->getFrom().getResource(); + if (presence->getType() == Presence::Unavailable) { + foreach (StringMUCOccupantPair occupantPair, occupants_) { + if (occupantPair.first == nick) { + occupants_.erase(nick); + onOccupantLeft(occupantPair.second, Part, ""); + break; + } + } + } else if (presence->getType() == Presence::Available) { + bool found = false; + foreach (StringMUCOccupantPair occupantPair, occupants_) { + if (occupantPair.first == nick) { + found = true; + break; + } + } + if (!found) { + MUCOccupant occupant(nick); + occupants_.insert(occupants_.end(), std::pair<String, MUCOccupant>(nick, occupant)); + onOccupantJoined(occupant); + } + onOccupantPresenceChange(presence); + } +} + + +} diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h new file mode 100644 index 0000000..45bcbd3 --- /dev/null +++ b/Swiften/MUC/MUC.h @@ -0,0 +1,49 @@ +#ifndef SWIFTEN_MUC_H +#define SWIFTEN_MUC_H + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/MUC/MUCOccupant.h" + +#include <boost/signals.hpp> +#include <boost/shared_ptr.hpp> + +#include <map> + +namespace Swift { + class StanzaChannel; + + class MUC { + public: + enum JoinResult { JoinSucceeded, JoinFailed }; + enum LeavingType { Part }; + + public: + MUC(StanzaChannel* stanzaChannel, const JID &muc); + ~MUC(); + + void joinAs(const String &nick); + String getCurrentNick(); + void part(); + void handleIncomingMessage(boost::shared_ptr<Message> message); + + public: + boost::signal<void (JoinResult)> onJoinComplete; + boost::signal<void (boost::shared_ptr<Message>)> onMessageReceived; + boost::signal<void (boost::shared_ptr<Presence>)> onOccupantPresenceChange; + boost::signal<void (const MUCOccupant&)> onOccupantJoined; + /**Occupant, type, and reason. */ + boost::signal<void (const MUCOccupant&, LeavingType, const String&)> onOccupantLeft; + + private: + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + JID muc_; + StanzaChannel *stanzaChannel_; + String myNick_; + std::map<String, MUCOccupant> occupants_; + }; +} + +#endif diff --git a/Swiften/MUC/MUCOccupant.cpp b/Swiften/MUC/MUCOccupant.cpp new file mode 100644 index 0000000..6ed8591 --- /dev/null +++ b/Swiften/MUC/MUCOccupant.cpp @@ -0,0 +1,15 @@ +#include "Swiften/MUC/MUCOccupant.h" + +namespace Swift { + +MUCOccupant::MUCOccupant(const String &nick) : nick_(nick) { +} + +MUCOccupant::~MUCOccupant() { +} + +String MUCOccupant::getNick() const { + return nick_; +} + +} diff --git a/Swiften/MUC/MUCOccupant.h b/Swiften/MUC/MUCOccupant.h new file mode 100644 index 0000000..22e58ac --- /dev/null +++ b/Swiften/MUC/MUCOccupant.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_MUCOccupant_H +#define SWIFTEN_MUCOccupant_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class Client; + + class MUCOccupant { + public: + MUCOccupant(const String &nick); + ~MUCOccupant(); + + String getNick() const; + + private: + String nick_; + }; +} + +#endif diff --git a/Swiften/MUC/Makefile.inc b/Swiften/MUC/Makefile.inc new file mode 100644 index 0000000..d97b9fa --- /dev/null +++ b/Swiften/MUC/Makefile.inc @@ -0,0 +1,3 @@ +SWIFTEN_SOURCES += \ + Swiften/MUC/MUC.cpp \ + Swiften/MUC/MUCOccupant.cpp diff --git a/Swiften/Makefile.inc b/Swiften/Makefile.inc new file mode 100644 index 0000000..880887b --- /dev/null +++ b/Swiften/Makefile.inc @@ -0,0 +1,42 @@ +include Swiften/Base/Makefile.inc +include Swiften/Application/Makefile.inc +include Swiften/EventLoop/Makefile.inc +include Swiften/StringCodecs/Makefile.inc +include Swiften/JID/Makefile.inc +include Swiften/Elements/Makefile.inc +include Swiften/Events/Makefile.inc +include Swiften/StreamStack/Makefile.inc +include Swiften/Serializer/Makefile.inc +include Swiften/Parser/Makefile.inc +include Swiften/MUC/Makefile.inc +include Swiften/Network/Makefile.inc +include Swiften/Client/Makefile.inc +include Swiften/TLS/Makefile.inc +include Swiften/SASL/Makefile.inc +include Swiften/Compress/Makefile.inc +include Swiften/Queries/Makefile.inc +include Swiften/Controllers/Makefile.inc +include Swiften/Roster/Makefile.inc +include Swiften/Disco/Makefile.inc +include Swiften/Presence/Makefile.inc +include Swiften/Notifier/Makefile.inc + +SWIFTEN_TARGET = Swiften/Swiften.a +SWIFTEN_OBJECTS = \ + $(SWIFTEN_SOURCES:.cpp=.o) \ + $(SWIFTEN_OBJECTIVE_SOURCES:.mm=.o) \ + $(LIBIDN_OBJECTS) \ + $(BOOST_OBJECTS) \ + $(ZLIB_OBJECTS) + +TARGETS += $(SWIFTEN_TARGET) +CLEANFILES += $(SWIFTEN_TARGET) $(SWIFTEN_OBJECTS) + +.PHONY: lib +lib: $(SWIFTEN_TARGET) + +$(SWIFTEN_TARGET): $(SWIFTEN_OBJECTS) + $(QUIET_AR)$(AR) $(ARFLAGS) $@ $(SWIFTEN_OBJECTS) + +include Swiften/Examples/Makefile.inc +include Swiften/QA/Makefile.inc diff --git a/Swiften/Network/BoostConnection.cpp b/Swiften/Network/BoostConnection.cpp new file mode 100644 index 0000000..f055f6a --- /dev/null +++ b/Swiften/Network/BoostConnection.cpp @@ -0,0 +1,100 @@ +#include "Swiften/Network/BoostConnection.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Network/DomainNameResolveException.h" + +namespace Swift { + +static const size_t BUFFER_SIZE = 4096; + +BoostConnection::BoostConnection(const String& domain) : + Connection(domain), ioService_(0), thread_(0), socket_(0), readBuffer_(BUFFER_SIZE) { + ioService_ = new boost::asio::io_service(); +} + +BoostConnection::~BoostConnection() { + MainEventLoop::removeEventsFromOwner(this); + ioService_->stop(); + thread_->join(); + delete socket_; + delete thread_; + delete ioService_; +} + +void BoostConnection::connect() { + thread_ = new boost::thread(boost::bind(&BoostConnection::doConnect, this)); +} + +void BoostConnection::disconnect() { + if (ioService_) { + ioService_->post(boost::bind(&BoostConnection::doDisconnect, this)); + } +} + +void BoostConnection::write(const ByteArray& data) { + if (ioService_) { + ioService_->post(boost::bind(&BoostConnection::doWrite, this, data)); + } +} + +void BoostConnection::doConnect() { + DomainNameResolver resolver; + try { + HostAddressPort addressPort = resolver.resolve(getDomain().getUTF8String()); + socket_ = new boost::asio::ip::tcp::socket(*ioService_); + boost::asio::ip::tcp::endpoint endpoint( + boost::asio::ip::address::from_string(addressPort.getAddress().toString()), addressPort.getPort()); + socket_->async_connect( + endpoint, + boost::bind(&BoostConnection::handleConnectFinished, this, boost::asio::placeholders::error)); + ioService_->run(); + } + catch (const DomainNameResolveException& e) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), DomainNameResolveError), this); + } +} + +void BoostConnection::handleConnectFinished(const boost::system::error_code& error) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onConnected)), this); + doRead(); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), ConnectionError), this); + } +} + +void BoostConnection::doRead() { + socket_->async_read_some( + boost::asio::buffer(readBuffer_), + boost::bind(&BoostConnection::handleSocketRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); +} + +void BoostConnection::doWrite(const ByteArray& data) { + boost::asio::write(*socket_, boost::asio::buffer(static_cast<const char*>(data.getData()), data.getSize())); +} + +void BoostConnection::handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred) { + if (!error) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDataRead), ByteArray(&readBuffer_[0], bytesTransferred)), this); + doRead(); + } + else if (error != boost::asio::error::operation_aborted) { + MainEventLoop::postEvent(boost::bind(boost::ref(onError), ReadError), this); + } +} + +void BoostConnection::doDisconnect() { + if (socket_) { + socket_->close(); + } +} + +} diff --git a/Swiften/Network/BoostConnection.h b/Swiften/Network/BoostConnection.h new file mode 100644 index 0000000..f8fa514 --- /dev/null +++ b/Swiften/Network/BoostConnection.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_BoostConnection_H +#define SWIFTEN_BoostConnection_H + +#include <boost/asio.hpp> + +#include "Swiften/Network/Connection.h" + +namespace boost { + class thread; + namespace system { + class error_code; + } +} + +namespace Swift { + class BoostConnection : public Connection { + public: + BoostConnection(const String& domain); + ~BoostConnection(); + + virtual void connect(); + virtual void disconnect(); + virtual void write(const ByteArray& data); + + private: + void doConnect(); + void doDisconnect(); + + void handleConnectFinished(const boost::system::error_code& error); + void handleSocketRead(const boost::system::error_code& error, size_t bytesTransferred); + void doRead(); + void doWrite(const ByteArray&); + + private: + boost::asio::io_service* ioService_; + boost::thread* thread_; + boost::asio::ip::tcp::socket* socket_; + std::vector<char> readBuffer_; + bool disconnecting_; + }; +} + +#endif diff --git a/Swiften/Network/BoostConnectionFactory.cpp b/Swiften/Network/BoostConnectionFactory.cpp new file mode 100644 index 0000000..9c542ac --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.cpp @@ -0,0 +1,12 @@ +#include "Swiften/Network/BoostConnectionFactory.h" + +namespace Swift { + +BoostConnectionFactory::BoostConnectionFactory() { +} + +BoostConnection* BoostConnectionFactory::createConnection(const String& domain) { + return new BoostConnection(domain); +} + +} diff --git a/Swiften/Network/BoostConnectionFactory.h b/Swiften/Network/BoostConnectionFactory.h new file mode 100644 index 0000000..b6a27b2 --- /dev/null +++ b/Swiften/Network/BoostConnectionFactory.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_BoostConnectionFactory_H +#define SWIFTEN_BoostConnectionFactory_H + +#include "Swiften/Network/ConnectionFactory.h" +#include "Swiften/Network/BoostConnection.h" + +namespace Swift { + class BoostConnection; + + class BoostConnectionFactory : public ConnectionFactory { + public: + BoostConnectionFactory(); + + virtual BoostConnection* createConnection(const String& domain); + }; +} + +#endif diff --git a/Swiften/Network/Connection.h b/Swiften/Network/Connection.h new file mode 100644 index 0000000..6d05eee --- /dev/null +++ b/Swiften/Network/Connection.h @@ -0,0 +1,40 @@ +#ifndef SWIFTEN_CONNECTION_H +#define SWIFTEN_CONNECTION_H + +#include <boost/signals.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class Connection { + public: + enum Error { + DomainNameResolveError, + ConnectionError, + ReadError + }; + + Connection(const String& domain) : domain_(domain) {} + virtual ~Connection() {} + + virtual void connect() = 0; + virtual void disconnect() = 0; + virtual void write(const ByteArray& data) = 0; + + public: + boost::signal<void ()> onConnected; + boost::signal<void (Error)> onError; + boost::signal<void (const ByteArray&)> onDataRead; + + protected: + const String& getDomain() const { + return domain_; + } + + private: + String domain_; + }; +} + +#endif diff --git a/Swiften/Network/ConnectionFactory.cpp b/Swiften/Network/ConnectionFactory.cpp new file mode 100644 index 0000000..686a165 --- /dev/null +++ b/Swiften/Network/ConnectionFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Network/ConnectionFactory.h" + +namespace Swift { + +ConnectionFactory::~ConnectionFactory() { +} + +} diff --git a/Swiften/Network/ConnectionFactory.h b/Swiften/Network/ConnectionFactory.h new file mode 100644 index 0000000..2af9ebf --- /dev/null +++ b/Swiften/Network/ConnectionFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ConnectionFactory_H +#define SWIFTEN_ConnectionFactory_H + +namespace Swift { + class String; + class Connection; + + class ConnectionFactory { + public: + virtual ~ConnectionFactory(); + + virtual Connection* createConnection(const String& domain) = 0; + }; +} + +#endif diff --git a/Swiften/Network/DomainNameResolveException.h b/Swiften/Network/DomainNameResolveException.h new file mode 100644 index 0000000..a6cfbc6 --- /dev/null +++ b/Swiften/Network/DomainNameResolveException.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_DOMAINNAMELOOKUPEXCEPTION_H +#define SWIFTEN_DOMAINNAMELOOKUPEXCEPTION_H + +namespace Swift { + class DomainNameResolveException { + public: + DomainNameResolveException() {} + }; +} + +#endif diff --git a/Swiften/Network/DomainNameResolver.cpp b/Swiften/Network/DomainNameResolver.cpp new file mode 100644 index 0000000..d2bbf0d --- /dev/null +++ b/Swiften/Network/DomainNameResolver.cpp @@ -0,0 +1,176 @@ +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Base/Platform.h" + +#include <stdlib.h> +#include <boost/asio.hpp> +#include <idna.h> +#ifdef SWIFTEN_PLATFORM_WINDOWS +#undef UNICODE +#include <windows.h> +#include <windns.h> +#ifndef DNS_TYPE_SRV +#define DNS_TYPE_SRV 33 +#endif +#else +#include <arpa/nameser.h> +#include <arpa/nameser_compat.h> +#include <resolv.h> +#endif + +#include "Swiften/Network/DomainNameResolveException.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + +DomainNameResolver::DomainNameResolver() { +} + +DomainNameResolver::~DomainNameResolver() { +} + +HostAddressPort DomainNameResolver::resolve(const String& domain) { + char* output; + if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) { + std::string outputString(output); + free(output); + return resolveDomain(outputString); + } + else { + return resolveDomain(domain.getUTF8String()); + } +} + +HostAddressPort DomainNameResolver::resolveDomain(const std::string& domain) { + try { + return resolveXMPPService(domain); + } + catch (const DomainNameResolveException&) { + } + return HostAddressPort(resolveHostName(domain), 5222); +} + +HostAddressPort DomainNameResolver::resolveXMPPService(const std::string& domain) { + std::string srvQuery = "_xmpp-client._tcp." + domain; + +#if defined(SWIFTEN_PLATFORM_WINDOWS) + DNS_RECORD* responses; + // FIXME: This conversion doesn't work if unicode is deffed above + if (DnsQuery(srvQuery.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &responses, NULL) != ERROR_SUCCESS) { + throw DomainNameResolveException(); + } + + DNS_RECORD* currentEntry = responses; + while (currentEntry) { + if (currentEntry->wType == DNS_TYPE_SRV) { + int port = currentEntry->Data.SRV.wPort; + try { + // The pNameTarget is actually a PCWSTR, so I would have expected this + // conversion to not work at all, but it does. + // Actually, it doesn't. Fix this and remove explicit cast + // Remove unicode undef above as well + std::string hostname((const char*) currentEntry->Data.SRV.pNameTarget); + HostAddress address = resolveHostName(hostname); + DnsRecordListFree(responses, DnsFreeRecordList); + return HostAddressPort(address, port); + } + catch (const DomainNameResolveException&) { + } + } + currentEntry = currentEntry->pNext; + } + DnsRecordListFree(responses, DnsFreeRecordList); + +#else + + ByteArray response; + response.resize(NS_PACKETSZ); + int responseLength = res_query(const_cast<char*>(srvQuery.c_str()), ns_c_in, ns_t_srv, reinterpret_cast<u_char*>(response.getData()), response.getSize()); + if (responseLength == -1) { + throw DomainNameResolveException(); + } + + // Parse header + HEADER* header = reinterpret_cast<HEADER*>(response.getData()); + unsigned char* messageStart = reinterpret_cast<unsigned char*>(response.getData()); + unsigned char* messageEnd = messageStart + responseLength; + unsigned char* currentEntry = messageStart + NS_HFIXEDSZ; + + // Skip over the queries + int queriesCount = ntohs(header->qdcount); + while (queriesCount > 0) { + int entryLength = dn_skipname(currentEntry, messageEnd); + if (entryLength < 0) { + throw DomainNameResolveException(); + } + currentEntry += entryLength + NS_QFIXEDSZ; + queriesCount--; + } + + // Process the SRV answers + int answersCount = ntohs(header->ancount); + while (answersCount > 0) { + int entryLength = dn_skipname(currentEntry, messageEnd); + currentEntry += entryLength; + currentEntry += NS_RRFIXEDSZ; + + // Uninteresting information + currentEntry += 2; // PRIORITY + currentEntry += 2; // WEIGHT + + // Port + if (currentEntry >= messageEnd) { + throw DomainNameResolveException(); + } + int port = ns_get16(currentEntry); + currentEntry += 2; + + // Hostname + if (currentEntry >= messageEnd) { + throw DomainNameResolveException(); + } + ByteArray entry; + entry.resize(NS_MAXDNAME); + entryLength = dn_expand(messageStart, messageEnd, currentEntry, entry.getData(), entry.getSize()); + if (entryLength < 0) { + throw DomainNameResolveException(); + } + try { + // Resolve the hostname + std::string hostname(entry.getData(), entryLength); + HostAddress address = resolveHostName(hostname); + return HostAddressPort(address, port); + } + catch (const DomainNameResolveException&) { + } + currentEntry += entryLength; + answersCount--; + } +#endif + + throw DomainNameResolveException(); +} + +HostAddress DomainNameResolver::resolveHostName(const std::string& hostname) { + boost::asio::io_service ioService; + boost::asio::ip::tcp::resolver resolver(ioService); + boost::asio::ip::tcp::resolver::query query(hostname, "5222"); + try { + boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query); + if (endpointIterator == boost::asio::ip::tcp::resolver::iterator()) { + throw DomainNameResolveException(); + } + boost::asio::ip::address address = (*endpointIterator).endpoint().address(); + if (address.is_v4()) { + return HostAddress(&address.to_v4().to_bytes()[0], 4); + } + else { + return HostAddress(&address.to_v6().to_bytes()[0], 16); + } + } + catch (...) { + throw DomainNameResolveException(); + } +} + +} diff --git a/Swiften/Network/DomainNameResolver.h b/Swiften/Network/DomainNameResolver.h new file mode 100644 index 0000000..c7736b1 --- /dev/null +++ b/Swiften/Network/DomainNameResolver.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_DOMAINNAMERESOLVER_H +#define SWIFTEN_DOMAINNAMERESOLVER_H + +#include <string> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/HostAddress.h" +#include "Swiften/Network/HostAddressPort.h" + +namespace Swift { + class String; + + class DomainNameResolver { + public: + DomainNameResolver(); + virtual ~DomainNameResolver(); + + HostAddressPort resolve(const String& domain); + + private: + virtual HostAddressPort resolveDomain(const std::string& domain); + HostAddressPort resolveXMPPService(const std::string& domain); + HostAddress resolveHostName(const std::string& hostName); + }; +} + +#endif diff --git a/Swiften/Network/HostAddress.cpp b/Swiften/Network/HostAddress.cpp new file mode 100644 index 0000000..84a0012 --- /dev/null +++ b/Swiften/Network/HostAddress.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Network/HostAddress.h" + +#include <boost/numeric/conversion/cast.hpp> +#include <cassert> +#include <sstream> +#include <iomanip> + +namespace Swift { + +HostAddress::HostAddress() { + for (int i = 0; i < 4; ++i) { + address_.push_back(0); + } +} + +HostAddress::HostAddress(const unsigned char* address, int length) { + assert(length == 4 || length == 16); + address_.reserve(length); + for (int i = 0; i < length; ++i) { + address_.push_back(address[i]); + } +} + +std::string HostAddress::toString() const { + if (address_.size() == 4) { + std::ostringstream result; + for (size_t i = 0; i < address_.size() - 1; ++i) { + result << boost::numeric_cast<unsigned int>(address_[i]) << "."; + } + result << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else if (address_.size() == 16) { + std::ostringstream result; + result << std::hex; + result.fill('0'); + for (size_t i = 0; i < (address_.size() / 2) - 1; ++i) { + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[2*i]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[(2*i)+1]) << ":"; + } + result << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 2]) << std::setw(2) << boost::numeric_cast<unsigned int>(address_[address_.size() - 1]); + return result.str(); + } + else { + assert(false); + return ""; + } +} + +} diff --git a/Swiften/Network/HostAddress.h b/Swiften/Network/HostAddress.h new file mode 100644 index 0000000..2c9760d --- /dev/null +++ b/Swiften/Network/HostAddress.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_HOSTADDRESS +#define SWIFTEN_HOSTADDRESS + +#include <string> +#include <vector> + +namespace Swift { + class HostAddress { + public: + HostAddress(); + HostAddress(const unsigned char* address, int length); + + const std::vector<unsigned char>& getRawAddress() const { + return address_; + } + + std::string toString() const; + + private: + std::vector<unsigned char> address_; + }; +} + +#endif diff --git a/Swiften/Network/HostAddressPort.h b/Swiften/Network/HostAddressPort.h new file mode 100644 index 0000000..8668ae4 --- /dev/null +++ b/Swiften/Network/HostAddressPort.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_HostAddressPort_H +#define SWIFTEN_HostAddressPort_H + +#include "Swiften/Network/HostAddress.h" + +namespace Swift { + class HostAddressPort { + public: + HostAddressPort(const HostAddress& address, int port) : address_(address), port_(port) { + } + + const HostAddress& getAddress() const { + return address_; + } + + int getPort() const { + return port_; + } + + private: + HostAddress address_; + int port_; + }; +} + +#endif diff --git a/Swiften/Network/Makefile.inc b/Swiften/Network/Makefile.inc new file mode 100644 index 0000000..055554a --- /dev/null +++ b/Swiften/Network/Makefile.inc @@ -0,0 +1,13 @@ +SWIFTEN_SOURCES += \ + Swiften/Network/HostAddress.cpp \ + Swiften/Network/DomainNameResolver.cpp \ + Swiften/Network/ConnectionFactory.cpp \ + Swiften/Network/BoostConnection.cpp \ + Swiften/Network/BoostConnectionFactory.cpp \ + Swiften/Network/Timer.cpp + +include Swiften/Network/UnitTest/Makefile.inc + +ifneq ($(WIN32),1) +LIBS += -lresolv +endif diff --git a/Swiften/Network/Timer.cpp b/Swiften/Network/Timer.cpp new file mode 100644 index 0000000..8b2b57f --- /dev/null +++ b/Swiften/Network/Timer.cpp @@ -0,0 +1,40 @@ +#include "Swiften/Network/Timer.h" + +#include <boost/date_time/posix_time/posix_time.hpp> + +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +Timer::Timer(int milliseconds) : + timeout_(milliseconds), ioService_(0), thread_(0), timer_(0) { + ioService_ = new boost::asio::io_service(); +} + +Timer::~Timer() { + MainEventLoop::removeEventsFromOwner(this); + ioService_->stop(); + thread_->join(); + delete timer_; + delete thread_; + delete ioService_; +} + +void Timer::start() { + thread_ = new boost::thread(boost::bind(&Timer::doStart, this)); +} + +void Timer::doStart() { + timer_ = new boost::asio::deadline_timer(*ioService_); + timer_->expires_from_now(boost::posix_time::milliseconds(timeout_)); + timer_->async_wait(boost::bind(&Timer::handleTimerTick, this)); + ioService_->run(); +} + +void Timer::handleTimerTick() { + MainEventLoop::postEvent(boost::bind(boost::ref(onTick)), this); + timer_->expires_from_now(boost::posix_time::milliseconds(timeout_)); + timer_->async_wait(boost::bind(&Timer::handleTimerTick, this)); +} + +} diff --git a/Swiften/Network/Timer.h b/Swiften/Network/Timer.h new file mode 100644 index 0000000..8e4b4c2 --- /dev/null +++ b/Swiften/Network/Timer.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_Timer_H +#define SWIFTEN_Timer_H + +#include <boost/asio.hpp> +#include <boost/signals.hpp> +#include <boost/thread.hpp> + +namespace Swift { + class Timer { + public: + Timer(int milliseconds); + ~Timer(); + + void start(); + + public: + boost::signal<void ()> onTick; + + private: + void doStart(); + void handleTimerTick(); + + private: + int timeout_; + boost::asio::io_service* ioService_; + boost::thread* thread_; + boost::asio::deadline_timer* timer_; + }; +} + +#endif diff --git a/Swiften/Network/UnitTest/HostAddressTest.cpp b/Swiften/Network/UnitTest/HostAddressTest.cpp new file mode 100644 index 0000000..b805647 --- /dev/null +++ b/Swiften/Network/UnitTest/HostAddressTest.cpp @@ -0,0 +1,33 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Network/HostAddress.h" + +using namespace Swift; + +class HostAddressTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(HostAddressTest); + CPPUNIT_TEST(testToString); + CPPUNIT_TEST(testToString_IPv6); + CPPUNIT_TEST_SUITE_END(); + + public: + HostAddressTest() {} + + void testToString() { + unsigned char address[4] = {10, 0, 1, 253}; + HostAddress testling(address, 4); + + CPPUNIT_ASSERT_EQUAL(std::string("10.0.1.253"), testling.toString()); + } + + void testToString_IPv6() { + unsigned char address[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}; + HostAddress testling(address, 16); + + CPPUNIT_ASSERT_EQUAL(std::string("0102:0304:0506:0708:090a:0b0c:0d0e:0f11"), testling.toString()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HostAddressTest); diff --git a/Swiften/Network/UnitTest/Makefile.inc b/Swiften/Network/UnitTest/Makefile.inc new file mode 100644 index 0000000..4f157f6 --- /dev/null +++ b/Swiften/Network/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Network/UnitTest/HostAddressTest.cpp diff --git a/Swiften/Notifier/GrowlNotifier.cpp b/Swiften/Notifier/GrowlNotifier.cpp new file mode 100644 index 0000000..13d3e6d --- /dev/null +++ b/Swiften/Notifier/GrowlNotifier.cpp @@ -0,0 +1,91 @@ +// FIXME: Is it safe to pass boost::function<void()> by raw values? +// FIXME: Should we release the strings created in the constructor? + +#include <cassert> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Notifier/GrowlNotifier.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace { + struct Context { + Context() {} + Context(const boost::function<void()>& callback) : callback(callback) {} + + boost::function<void()> callback; + }; + + void notificationClicked(CFPropertyListRef growlContext) { + Context context; + + CFDataRef growlContextData = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) growlContext, 0); + assert(CFDataGetLength(growlContextData) == sizeof(Context)); + CFDataGetBytes(growlContextData, CFRangeMake(0, CFDataGetLength(growlContextData)), (UInt8*) &context); + + context.callback(); + } + + void notificationTimedout(CFPropertyListRef) { + } +} + +namespace Swift { + +GrowlNotifier::GrowlNotifier(const String& name) { + // All notifications + CFMutableArrayRef allNotifications = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactAvailable))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactUnavailable))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactStatusChange))); + CFArrayAppendValue(allNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(IncomingMessage))); + + // Default Notifications + CFMutableArrayRef defaultNotifications = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendValue(defaultNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(ContactAvailable))); + CFArrayAppendValue(defaultNotifications, SWIFTEN_STRING_TO_CFSTRING(typeToString(IncomingMessage))); + + // Initialize delegate + InitGrowlDelegate(&delegate_); + delegate_.applicationName = SWIFTEN_STRING_TO_CFSTRING(name); + CFTypeRef keys[] = { GROWL_NOTIFICATIONS_ALL, GROWL_NOTIFICATIONS_DEFAULT }; + CFTypeRef values[] = { allNotifications, defaultNotifications }; + delegate_.registrationDictionary = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + delegate_.growlNotificationWasClicked = ¬ificationClicked; + delegate_.growlNotificationTimedOut = ¬ificationTimedout; + Growl_SetDelegate(&delegate_); +} + +void GrowlNotifier::showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) { + CFStringRef cfSubject = SWIFTEN_STRING_TO_CFSTRING(subject); + CFStringRef cfDescription = SWIFTEN_STRING_TO_CFSTRING(description); + CFStringRef cfName = SWIFTEN_STRING_TO_CFSTRING(typeToString(type)); + CFDataRef cfIcon = CFDataCreate( NULL, (UInt8*) picture.getData(), picture.getSize()); + + Context context(callback); + CFDataRef cfContextData[1]; + cfContextData[0] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &context, sizeof(Context)); + CFArrayRef cfContext = CFArrayCreate( kCFAllocatorDefault, (const void **) cfContextData, 1, &kCFTypeArrayCallBacks ); + CFRelease(cfContextData[0]); + + Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cfSubject, cfDescription, cfName, cfIcon, 0, false, cfContext); + + CFRelease(cfContext); + CFRelease(cfIcon); + CFRelease(cfName); + CFRelease(cfDescription); + CFRelease(cfSubject); +} + +String GrowlNotifier::typeToString(Type type) { + switch (type) { + case ContactAvailable: return "Contact Becomes Available"; + case ContactUnavailable: return "Contact Becomes Unavailable"; + case ContactStatusChange: return "Contact Changes Status"; + case IncomingMessage: return "Incoming Message"; + } + assert(false); + return ""; +} + +} diff --git a/Swiften/Notifier/GrowlNotifier.h b/Swiften/Notifier/GrowlNotifier.h new file mode 100644 index 0000000..3bf53be --- /dev/null +++ b/Swiften/Notifier/GrowlNotifier.h @@ -0,0 +1,28 @@ +#pragma once + +#include <CoreFoundation/CoreFoundation.h> +#include <Growl/Growl.h> + +#include "Swiften/Notifier/Notifier.h" + +namespace Swift { + /** + * Preconditions for using growlnotifier: + * - Must be part a bundle. + * - The Carbon/Cocoa application loop must be running (e.g. through QApplication) + * such that notifications are coming through. + * TODO: Find out what the easiest way is to do this without a QApplication. + */ + class GrowlNotifier : public Notifier { + public: + GrowlNotifier(const String& name); + + virtual void showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback); + + private: + String typeToString(Type type); + + private: + Growl_Delegate delegate_; + }; +} diff --git a/Swiften/Notifier/Makefile.inc b/Swiften/Notifier/Makefile.inc new file mode 100644 index 0000000..6d7e79a --- /dev/null +++ b/Swiften/Notifier/Makefile.inc @@ -0,0 +1,7 @@ +SWIFTEN_SOURCES += \ + Swiften/Notifier/Notifier.cpp + +ifeq ($(HAVE_GROWL),yes) + SWIFTEN_SOURCES += \ + Swiften/Notifier/GrowlNotifier.cpp +endif diff --git a/Swiften/Notifier/Notifier.cpp b/Swiften/Notifier/Notifier.cpp new file mode 100644 index 0000000..88cb0ee --- /dev/null +++ b/Swiften/Notifier/Notifier.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Notifier/Notifier.h" + +namespace Swift { + +Notifier::~Notifier() { +} + +} diff --git a/Swiften/Notifier/Notifier.h b/Swiften/Notifier/Notifier.h new file mode 100644 index 0000000..2ab3ba8 --- /dev/null +++ b/Swiften/Notifier/Notifier.h @@ -0,0 +1,24 @@ +#pragma once + +#include <boost/function.hpp> + +#include "Swiften/Base/String.h" + +namespace Swift { + class Notifier { + public: + virtual ~Notifier(); + + enum Type { ContactAvailable, ContactUnavailable, ContactStatusChange, IncomingMessage }; + + /** + * Picture is a PNG image. + */ + virtual void showMessage( + Type type, + const String& subject, + const String& description, + const ByteArray& picture, + boost::function<void()> callback) = 0; + }; +} diff --git a/Swiften/Parser/AttributeMap.h b/Swiften/Parser/AttributeMap.h new file mode 100644 index 0000000..6662b98 --- /dev/null +++ b/Swiften/Parser/AttributeMap.h @@ -0,0 +1,25 @@ +#ifndef ATTRIBUTEMAP_H +#define ATTRIBUTEMAP_H + +#include <map> + +#include "Swiften/Base/String.h" + +namespace Swift { + class AttributeMap : public std::map<String,String> { + public: + AttributeMap() {} + + String getAttribute(const String& attribute) const { + AttributeMap::const_iterator i = find(attribute); + if (i == end()) { + return ""; + } + else { + return i->second; + } + } + }; +} + +#endif diff --git a/Swiften/Parser/AuthFailureParser.h b/Swiften/Parser/AuthFailureParser.h new file mode 100644 index 0000000..3a950ef --- /dev/null +++ b/Swiften/Parser/AuthFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_AuthFailureParser_H +#define SWIFTEN_AuthFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthFailure.h" + +namespace Swift { + class AuthFailureParser : public GenericElementParser<AuthFailure> { + public: + AuthFailureParser() : GenericElementParser<AuthFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/AuthRequestParser.cpp b/Swiften/Parser/AuthRequestParser.cpp new file mode 100644 index 0000000..5338b88 --- /dev/null +++ b/Swiften/Parser/AuthRequestParser.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Parser/AuthRequestParser.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthRequestParser::AuthRequestParser() : GenericElementParser<AuthRequest>(), depth_(0) { +} + +void AuthRequestParser::handleStartElement(const String&, const String&, const AttributeMap& attribute) { + if (depth_ == 0) { + getElementGeneric()->setMechanism(attribute.getAttribute("mechanism")); + } + ++depth_; +} + +void AuthRequestParser::handleEndElement(const String&, const String&) { + --depth_; + if (depth_ == 0) { + getElementGeneric()->setMessage(Base64::decode(text_)); + } +} + +void AuthRequestParser::handleCharacterData(const String& text) { + text_ += text; +} + +} diff --git a/Swiften/Parser/AuthRequestParser.h b/Swiften/Parser/AuthRequestParser.h new file mode 100644 index 0000000..8916922 --- /dev/null +++ b/Swiften/Parser/AuthRequestParser.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_AuthRequestParser_H +#define SWIFTEN_AuthRequestParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class AuthRequestParser : public GenericElementParser<AuthRequest> { + public: + AuthRequestParser(); + + virtual void handleStartElement(const String&, const String& ns, const AttributeMap&); + virtual void handleEndElement(const String&, const String& ns); + virtual void handleCharacterData(const String&); + + private: + String text_; + int depth_; + }; +} + +#endif diff --git a/Swiften/Parser/AuthSuccessParser.h b/Swiften/Parser/AuthSuccessParser.h new file mode 100644 index 0000000..bb6515d --- /dev/null +++ b/Swiften/Parser/AuthSuccessParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_AUTHSUCCESSPARSER_H +#define SWIFTEN_AUTHSUCCESSPARSER_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/AuthSuccess.h" + +namespace Swift { + class AuthSuccessParser : public GenericElementParser<AuthSuccess> { + public: + AuthSuccessParser() : GenericElementParser<AuthSuccess>() {} + }; +} + +#endif diff --git a/Swiften/Parser/CompressFailureParser.h b/Swiften/Parser/CompressFailureParser.h new file mode 100644 index 0000000..d53e0ef --- /dev/null +++ b/Swiften/Parser/CompressFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_CompressFailureParser_H +#define SWIFTEN_CompressFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/CompressFailure.h" + +namespace Swift { + class CompressFailureParser : public GenericElementParser<CompressFailure> { + public: + CompressFailureParser() : GenericElementParser<CompressFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/CompressParser.cpp b/Swiften/Parser/CompressParser.cpp new file mode 100644 index 0000000..7ca752d --- /dev/null +++ b/Swiften/Parser/CompressParser.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Parser/CompressParser.h" + +namespace Swift { + +CompressParser::CompressParser() : GenericElementParser<CompressRequest>(), currentDepth_(0), inMethod_(false) { +} + +void CompressParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + if (currentDepth_ == 1 && element == "method") { + inMethod_ = true; + currentText_ = ""; + } + ++currentDepth_; +} + +void CompressParser::handleEndElement(const String&, const String&) { + --currentDepth_; + if (currentDepth_ == 1 && inMethod_) { + getElementGeneric()->setMethod(currentText_); + inMethod_ = false; + } +} + +void CompressParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/CompressParser.h b/Swiften/Parser/CompressParser.h new file mode 100644 index 0000000..8931778 --- /dev/null +++ b/Swiften/Parser/CompressParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_CompressParser_H +#define SWIFTEN_CompressParser_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/CompressRequest.h" + +namespace Swift { + class CompressParser : public GenericElementParser<CompressRequest> { + public: + CompressParser(); + + private: + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + private: + int currentDepth_; + String currentText_; + bool inMethod_; + }; +} + +#endif diff --git a/Swiften/Parser/CompressedParser.h b/Swiften/Parser/CompressedParser.h new file mode 100644 index 0000000..365f619 --- /dev/null +++ b/Swiften/Parser/CompressedParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_COMPRESSEDPARSER_H +#define SWIFTEN_COMPRESSEDPARSER_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/Compressed.h" + +namespace Swift { + class CompressedParser : public GenericElementParser<Compressed> { + public: + CompressedParser() : GenericElementParser<Compressed>() {} + }; +} + +#endif diff --git a/Swiften/Parser/ElementParser.cpp b/Swiften/Parser/ElementParser.cpp new file mode 100644 index 0000000..1c04d92 --- /dev/null +++ b/Swiften/Parser/ElementParser.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/ElementParser.h" + +namespace Swift { + +ElementParser::~ElementParser() { +} + +} diff --git a/Swiften/Parser/ElementParser.h b/Swiften/Parser/ElementParser.h new file mode 100644 index 0000000..3848f0c --- /dev/null +++ b/Swiften/Parser/ElementParser.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_ElementParser_H +#define SWIFTEN_ElementParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class ElementParser { + public: + virtual ~ElementParser(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + + virtual boost::shared_ptr<Element> getElement() const = 0; + }; +} + +#endif diff --git a/Swiften/Parser/ExpatParser.cpp b/Swiften/Parser/ExpatParser.cpp new file mode 100644 index 0000000..6d23c93 --- /dev/null +++ b/Swiften/Parser/ExpatParser.cpp @@ -0,0 +1,66 @@ +#include "Swiften/Parser/ExpatParser.h" + +#include <iostream> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +static const char NAMESPACE_SEPARATOR = '\x01'; + +static void handleStartElement(void* client, const XML_Char* name, const XML_Char** attributes) { + std::pair<String,String> nsTagPair = String(name).getSplittedAtFirst(NAMESPACE_SEPARATOR); + if (nsTagPair.second == "") { + nsTagPair.second = nsTagPair.first; + nsTagPair.first = ""; + } + AttributeMap attributeValues; + const XML_Char** currentAttribute = attributes; + while (*currentAttribute) { + std::pair<String,String> nsAttributePair = String(*currentAttribute).getSplittedAtFirst(NAMESPACE_SEPARATOR); + if (nsAttributePair.second == "") { + nsAttributePair.second = nsAttributePair.first; + nsAttributePair.first = ""; + } + attributeValues[nsAttributePair.second] = String(*(currentAttribute+1)); + currentAttribute += 2; + } + + static_cast<XMLParserClient*>(client)->handleStartElement(nsTagPair.second, nsTagPair.first, attributeValues); +} + +static void handleEndElement(void* client, const XML_Char* name) { + std::pair<String,String> nsTagPair = String(name).getSplittedAtFirst(NAMESPACE_SEPARATOR); + static_cast<XMLParserClient*>(client)->handleEndElement(nsTagPair.second, nsTagPair.first); +} + +static void handleCharacterData(void* client, const XML_Char* data, int len) { + static_cast<XMLParserClient*>(client)->handleCharacterData(String(data, len)); +} + +static void handleXMLDeclaration(void*, const XML_Char*, const XML_Char*, int) { +} + + +ExpatParser::ExpatParser(XMLParserClient* client) : XMLParser(client) { + parser_ = XML_ParserCreateNS("UTF-8", NAMESPACE_SEPARATOR); + XML_SetUserData(parser_, client); + XML_SetElementHandler(parser_, handleStartElement, handleEndElement); + XML_SetCharacterDataHandler(parser_, handleCharacterData); + XML_SetXmlDeclHandler(parser_, handleXMLDeclaration); +} + +ExpatParser::~ExpatParser() { + XML_ParserFree(parser_); +} + +bool ExpatParser::parse(const String& data) { + bool success = XML_Parse(parser_, data.getUTF8Data(), data.getUTF8Size(), false) == XML_STATUS_OK; + /*if (!success) { + std::cout << "ERROR: " << XML_ErrorString(XML_GetErrorCode(parser_)) << " while parsing " << data << std::endl; + }*/ + return success; +} + +} diff --git a/Swiften/Parser/ExpatParser.h b/Swiften/Parser/ExpatParser.h new file mode 100644 index 0000000..2b5e646 --- /dev/null +++ b/Swiften/Parser/ExpatParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_ExpatParser_H +#define SWIFTEN_ExpatParser_H + +#include <expat.h> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class ExpatParser : public XMLParser, public boost::noncopyable { + public: + ExpatParser(XMLParserClient* client); + ~ExpatParser(); + + bool parse(const String& data); + + private: + XML_Parser parser_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericElementParser.h b/Swiften/Parser/GenericElementParser.h new file mode 100644 index 0000000..e1b9cf7 --- /dev/null +++ b/Swiften/Parser/GenericElementParser.h @@ -0,0 +1,43 @@ +#ifndef SWIFTEN_GenericElementParser_H +#define SWIFTEN_GenericElementParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/ElementParser.h" + +namespace Swift { + class String; + class PayloadParserFactoryCollection; + + template<typename ElementType> + class GenericElementParser : public ElementParser { + public: + GenericElementParser() { + stanza_ = boost::shared_ptr<ElementType>(new ElementType()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + protected: + virtual boost::shared_ptr<ElementType> getElementGeneric() const { + return stanza_; + } + + private: + virtual void handleStartElement(const String&, const String&, const AttributeMap&) { + } + + virtual void handleEndElement(const String&, const String&) { + } + + virtual void handleCharacterData(const String&) { + } + + private: + boost::shared_ptr<ElementType> stanza_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericPayloadParser.h b/Swiften/Parser/GenericPayloadParser.h new file mode 100644 index 0000000..a07b795 --- /dev/null +++ b/Swiften/Parser/GenericPayloadParser.h @@ -0,0 +1,32 @@ +#ifndef GENERICPAYLOADPARSER_H +#define GENERICPAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + class String; + + template<typename PAYLOAD_TYPE> + class GenericPayloadParser : public PayloadParser { + public: + GenericPayloadParser() : PayloadParser() { + payload_ = boost::shared_ptr<PAYLOAD_TYPE>(new PAYLOAD_TYPE()); + } + + virtual boost::shared_ptr<Payload> getPayload() const { + return payload_; + } + + protected: + virtual boost::shared_ptr<PAYLOAD_TYPE> getPayloadInternal() const { + return payload_; + } + + private: + boost::shared_ptr<PAYLOAD_TYPE> payload_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericPayloadParserFactory.h b/Swiften/Parser/GenericPayloadParserFactory.h new file mode 100644 index 0000000..d537b46 --- /dev/null +++ b/Swiften/Parser/GenericPayloadParserFactory.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_GENERICPAYLOADPARSERFACTORY_H +#define SWIFTEN_GENERICPAYLOADPARSERFACTORY_H + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Base/String.h" + +namespace Swift { + + template<typename PARSER_TYPE> + class GenericPayloadParserFactory : public PayloadParserFactory { + public: + GenericPayloadParserFactory(const String& tag, const String& xmlns = "") : tag_(tag), xmlns_(xmlns) {} + + virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const { + return element == tag_ && (xmlns_.isEmpty() ? true : xmlns_ == ns); + } + + virtual PayloadParser* createPayloadParser() { + return new PARSER_TYPE(); + } + + private: + String tag_; + String xmlns_; + }; +} + +#endif diff --git a/Swiften/Parser/GenericStanzaParser.h b/Swiften/Parser/GenericStanzaParser.h new file mode 100644 index 0000000..ba1807a --- /dev/null +++ b/Swiften/Parser/GenericStanzaParser.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_GenericStanzaParser_H +#define SWIFTEN_GenericStanzaParser_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/StanzaParser.h" + +namespace Swift { + class String; + class PayloadParserFactoryCollection; + + template<typename STANZA_TYPE> + class GenericStanzaParser : public StanzaParser { + public: + GenericStanzaParser(PayloadParserFactoryCollection* collection) : + StanzaParser(collection) { + stanza_ = boost::shared_ptr<STANZA_TYPE>(new STANZA_TYPE()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + virtual boost::shared_ptr<STANZA_TYPE> getStanzaGeneric() const { + return stanza_; + } + + private: + boost::shared_ptr<STANZA_TYPE> stanza_; + }; +} + +#endif diff --git a/Swiften/Parser/IQParser.cpp b/Swiften/Parser/IQParser.cpp new file mode 100644 index 0000000..2b4b364 --- /dev/null +++ b/Swiften/Parser/IQParser.cpp @@ -0,0 +1,33 @@ +#include <iostream> + +#include "Swiften/Parser/IQParser.h" + +namespace Swift { + +IQParser::IQParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<IQ>(factories) { +} + +void IQParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "set") { + getStanzaGeneric()->setType(IQ::Set); + } + else if (type->second == "get") { + getStanzaGeneric()->setType(IQ::Get); + } + else if (type->second == "result") { + getStanzaGeneric()->setType(IQ::Result); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(IQ::Error); + } + else { + std::cerr << "Unknown IQ type: " << type->second << std::endl; + getStanzaGeneric()->setType(IQ::Get); + } + } +} + +} diff --git a/Swiften/Parser/IQParser.h b/Swiften/Parser/IQParser.h new file mode 100644 index 0000000..cd2fc05 --- /dev/null +++ b/Swiften/Parser/IQParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_IQParser_H +#define SWIFTEN_IQParser_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQParser : public GenericStanzaParser<IQ> { + public: + IQParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/LibXMLParser.cpp b/Swiften/Parser/LibXMLParser.cpp new file mode 100644 index 0000000..8f14f21 --- /dev/null +++ b/Swiften/Parser/LibXMLParser.cpp @@ -0,0 +1,63 @@ +#include "Swiften/Parser/LibXMLParser.h" + +#include <iostream> +#include <cassert> +#include <cstring> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +static void handleStartElement(void *client, const xmlChar* name, const xmlChar** attributes) { + AttributeMap attributeValues; + const xmlChar** currentAttribute = attributes; + if (currentAttribute) { + while (*currentAttribute) { + attributeValues[String(reinterpret_cast<const char*>(*currentAttribute))] = String(reinterpret_cast<const char*>(*(currentAttribute+1))); + currentAttribute += 2; + } + } + static_cast<XMLParserClient*>(client)->handleStartElement(reinterpret_cast<const char*>(name), attributeValues); +} + +static void handleEndElement(void *client, const xmlChar* name) { + static_cast<XMLParserClient*>(client)->handleEndElement(reinterpret_cast<const char*>(name)); +} + +static void handleCharacterData(void* client, const xmlChar* data, int len) { + static_cast<XMLParserClient*>(client)->handleCharacterData(String(reinterpret_cast<const char*>(data), len)); +} + +static void handleError(void*, const char*, ... ) { +} + +static void handleWarning(void*, const char*, ... ) { +} + + + +LibXMLParser::LibXMLParser(XMLParserClient* client) : XMLParser(client) { + memset(&handler_, 0, sizeof(handler_) ); + handler_.initialized = XML_SAX2_MAGIC; + handler_.startElement = &handleStartElement; + handler_.endElement = &handleEndElement; + handler_.characters = &handleCharacterData; + handler_.warning = &handleWarning; + handler_.error = &handleError; + + context_ = xmlCreatePushParserCtxt(&handler_, client, 0, 0, 0); + assert(context_); +} + +LibXMLParser::~LibXMLParser() { + if (context_) { + xmlFreeParserCtxt(context_); + } +} + +bool LibXMLParser::parse(const String& data) { + return xmlParseChunk(context_, data.getUTF8Data(), data.getUTF8Size(), false) == XML_ERR_OK; +} + +} diff --git a/Swiften/Parser/LibXMLParser.h b/Swiften/Parser/LibXMLParser.h new file mode 100644 index 0000000..f8faef6 --- /dev/null +++ b/Swiften/Parser/LibXMLParser.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_LibXMLParser_H +#define SWIFTEN_LibXMLParser_H + +#include <libxml/parser.h> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class LibXMLParser : public XMLParser, public boost::noncopyable { + public: + LibXMLParser(XMLParserClient* client); + ~LibXMLParser(); + + bool parse(const String& data); + + private: + xmlSAXHandler handler_; + xmlParserCtxtPtr context_; + }; +} + +#endif diff --git a/Swiften/Parser/Makefile.inc b/Swiften/Parser/Makefile.inc new file mode 100644 index 0000000..f47ae29 --- /dev/null +++ b/Swiften/Parser/Makefile.inc @@ -0,0 +1,32 @@ +SWIFTEN_SOURCES += \ + Swiften/Parser/XMLParser.cpp \ + Swiften/Parser/XMLParserClient.cpp \ + Swiften/Parser/XMLParserFactory.cpp \ + Swiften/Parser/PlatformXMLParserFactory.cpp \ + Swiften/Parser/XMPPParser.cpp \ + Swiften/Parser/XMPPParserClient.cpp \ + Swiften/Parser/MessageParser.cpp \ + Swiften/Parser/IQParser.cpp \ + Swiften/Parser/PresenceParser.cpp \ + Swiften/Parser/StreamFeaturesParser.cpp \ + Swiften/Parser/CompressParser.cpp \ + Swiften/Parser/AuthRequestParser.cpp \ + Swiften/Parser/StanzaParser.cpp \ + Swiften/Parser/ElementParser.cpp \ + Swiften/Parser/PayloadParser.cpp \ + Swiften/Parser/PayloadParserFactory.cpp \ + Swiften/Parser/PayloadParserFactoryCollection.cpp \ + Swiften/Parser/SerializingParser.cpp + +ifeq ($(HAVE_LIBXML),yes) +SWIFTEN_SOURCES += \ + Swiften/Parser/LibXMLParser.cpp +endif + +ifeq ($(HAVE_EXPAT),yes) +SWIFTEN_SOURCES += \ + Swiften/Parser/ExpatParser.cpp +endif + +include Swiften/Parser/PayloadParsers/Makefile.inc +include Swiften/Parser/UnitTest/Makefile.inc diff --git a/Swiften/Parser/MessageParser.cpp b/Swiften/Parser/MessageParser.cpp new file mode 100644 index 0000000..5e83fad --- /dev/null +++ b/Swiften/Parser/MessageParser.cpp @@ -0,0 +1,33 @@ +#include <iostream> + +#include "Swiften/Parser/MessageParser.h" + +namespace Swift { + +MessageParser::MessageParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<Message>(factories) { + getStanzaGeneric()->setType(Message::Normal); +} + +void MessageParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "chat") { + getStanzaGeneric()->setType(Message::Chat); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(Message::Error); + } + else if (type->second == "groupchat") { + getStanzaGeneric()->setType(Message::Groupchat); + } + else if (type->second == "headline") { + getStanzaGeneric()->setType(Message::Headline); + } + else { + getStanzaGeneric()->setType(Message::Normal); + } + } +} + +} diff --git a/Swiften/Parser/MessageParser.h b/Swiften/Parser/MessageParser.h new file mode 100644 index 0000000..05c9280 --- /dev/null +++ b/Swiften/Parser/MessageParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_MESSAGEPARSER_H +#define SWIFTEN_MESSAGEPARSER_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/Message.h" + +namespace Swift { + class MessageParser : public GenericStanzaParser<Message> { + public: + MessageParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParser.cpp b/Swiften/Parser/PayloadParser.cpp new file mode 100644 index 0000000..072edef --- /dev/null +++ b/Swiften/Parser/PayloadParser.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + +PayloadParser::~PayloadParser() { +} + +} diff --git a/Swiften/Parser/PayloadParser.h b/Swiften/Parser/PayloadParser.h new file mode 100644 index 0000000..fc1e1c8 --- /dev/null +++ b/Swiften/Parser/PayloadParser.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_PAYLOADPARSER_H +#define SWIFTEN_PAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> +#include "Swiften/Parser/AttributeMap.h" + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class String; + + class PayloadParser { + public: + virtual ~PayloadParser(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + + virtual boost::shared_ptr<Payload> getPayload() const = 0; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParserFactory.cpp b/Swiften/Parser/PayloadParserFactory.cpp new file mode 100644 index 0000000..b31f8ae --- /dev/null +++ b/Swiften/Parser/PayloadParserFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + +PayloadParserFactory::~PayloadParserFactory() { +} + +} diff --git a/Swiften/Parser/PayloadParserFactory.h b/Swiften/Parser/PayloadParserFactory.h new file mode 100644 index 0000000..728b4e8 --- /dev/null +++ b/Swiften/Parser/PayloadParserFactory.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_PAYLOADPARSERFACTORY_H +#define SWIFTEN_PAYLOADPARSERFACTORY_H + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class String; + class PayloadParser; + + class PayloadParserFactory { + public: + virtual ~PayloadParserFactory(); + + virtual bool canParse(const String& element, const String& ns, const AttributeMap& attributes) const = 0; + virtual PayloadParser* createPayloadParser() = 0; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParserFactoryCollection.cpp new file mode 100644 index 0000000..f361ce8 --- /dev/null +++ b/Swiften/Parser/PayloadParserFactoryCollection.cpp @@ -0,0 +1,27 @@ +#include <boost/bind.hpp> +#include <algorithm> + +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + +PayloadParserFactoryCollection::PayloadParserFactoryCollection() { +} + +void PayloadParserFactoryCollection::addFactory(PayloadParserFactory* factory) { + factories_.push_back(factory); +} + +void PayloadParserFactoryCollection::removeFactory(PayloadParserFactory* factory) { + factories_.erase(remove(factories_.begin(), factories_.end(), factory), factories_.end()); +} + +PayloadParserFactory* PayloadParserFactoryCollection::getPayloadParserFactory(const String& element, const String& ns, const AttributeMap& attributes) { + std::vector<PayloadParserFactory*>::const_iterator i = std::find_if( + factories_.begin(), factories_.end(), + boost::bind(&PayloadParserFactory::canParse, _1, element, ns, attributes)); + return (i != factories_.end() ? *i : NULL); +} + +} diff --git a/Swiften/Parser/PayloadParserFactoryCollection.h b/Swiften/Parser/PayloadParserFactoryCollection.h new file mode 100644 index 0000000..fad94c3 --- /dev/null +++ b/Swiften/Parser/PayloadParserFactoryCollection.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_PAYLOADPARSERFACTORYCOLLECTION_H +#define SWIFTEN_PAYLOADPARSERFACTORYCOLLECTION_H + +#include <vector> + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class PayloadParserFactory; + class String; + + class PayloadParserFactoryCollection { + public: + PayloadParserFactoryCollection(); + + void addFactory(PayloadParserFactory* factory); + void removeFactory(PayloadParserFactory* factory); + PayloadParserFactory* getPayloadParserFactory(const String& element, const String& ns, const AttributeMap& attributes); + + private: + std::vector<PayloadParserFactory*> factories_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/BodyParser.cpp b/Swiften/Parser/PayloadParsers/BodyParser.cpp new file mode 100644 index 0000000..e5898ff --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParser.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Parser/PayloadParsers/BodyParser.h" + +namespace Swift { + +BodyParser::BodyParser() : level_(0) { +} + +void BodyParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void BodyParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setText(text_); + } +} + +void BodyParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/BodyParser.h b/Swiften/Parser/PayloadParsers/BodyParser.h new file mode 100644 index 0000000..2d272ea --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_BodyParser_H +#define SWIFTEN_BodyParser_H + +#include "Swiften/Elements/Body.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class BodyParser : public GenericPayloadParser<Body> { + public: + BodyParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/BodyParserFactory.h b/Swiften/Parser/PayloadParsers/BodyParserFactory.h new file mode 100644 index 0000000..3da7393 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BodyParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_BodyParserFACTORY_H +#define SWIFTEN_BodyParserFACTORY_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BodyParser.h" + +namespace Swift { + class BodyParserFactory : public GenericPayloadParserFactory<BodyParser> { + public: + BodyParserFactory() : GenericPayloadParserFactory<BodyParser>("body") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp b/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp new file mode 100644 index 0000000..ffa24ad --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" + +namespace Swift { + +DiscoInfoParser::DiscoInfoParser() : level_(TopLevel) { +} + +void DiscoInfoParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level_ == PayloadLevel) { + if (element == "identity") { + getPayloadInternal()->addIdentity(DiscoInfo::Identity(attributes.getAttribute("name"), attributes.getAttribute("category"), attributes.getAttribute("type"), attributes.getAttribute("lang"))); + } + else if (element == "feature") { + getPayloadInternal()->addFeature(attributes.getAttribute("var")); + } + } + ++level_; +} + +void DiscoInfoParser::handleEndElement(const String&, const String&) { + --level_; +} + +void DiscoInfoParser::handleCharacterData(const String&) { +} + +} diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParser.h b/Swiften/Parser/PayloadParsers/DiscoInfoParser.h new file mode 100644 index 0000000..b7be972 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_DiscoInfoParser_H +#define SWIFTEN_DiscoInfoParser_H + +#include "Swiften/Elements/DiscoInfo.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class DiscoInfoParser : public GenericPayloadParser<DiscoInfo> { + public: + DiscoInfoParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h b/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h new file mode 100644 index 0000000..ef1c31c --- /dev/null +++ b/Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_DiscoInfoParserFactory_H +#define SWIFTEN_DiscoInfoParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" + +namespace Swift { + class DiscoInfoParserFactory : public GenericPayloadParserFactory<DiscoInfoParser> { + public: + DiscoInfoParserFactory() : GenericPayloadParserFactory<DiscoInfoParser>("query", "http://jabber.org/protocol/disco#info") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ErrorParser.cpp b/Swiften/Parser/PayloadParsers/ErrorParser.cpp new file mode 100644 index 0000000..13380c8 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParser.cpp @@ -0,0 +1,109 @@ +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" + +namespace Swift { + +ErrorParser::ErrorParser() : level_(TopLevel) { +} + +void ErrorParser::handleStartElement(const String&, const String&, const AttributeMap& attributes) { + if (level_ == TopLevel) { + String type = attributes.getAttribute("type"); + if (type == "continue") { + getPayloadInternal()->setType(Error::Continue); + } + else if (type == "modify") { + getPayloadInternal()->setType(Error::Modify); + } + else if (type == "auth") { + getPayloadInternal()->setType(Error::Auth); + } + else if (type == "wait") { + getPayloadInternal()->setType(Error::Wait); + } + else { + getPayloadInternal()->setType(Error::Cancel); + } + } + ++level_; +} + +void ErrorParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (element == "text") { + getPayloadInternal()->setText(currentText_); + } + else if (element == "bad-request") { + getPayloadInternal()->setCondition(Error::BadRequest); + } + else if (element == "conflict") { + getPayloadInternal()->setCondition(Error::Conflict); + } + else if (element == "feature-not-implemented") { + getPayloadInternal()->setCondition(Error::FeatureNotImplemented); + } + else if (element == "forbidden") { + getPayloadInternal()->setCondition(Error::Forbidden); + } + else if (element == "gone") { + getPayloadInternal()->setCondition(Error::Gone); + } + else if (element == "internal-server-error") { + getPayloadInternal()->setCondition(Error::InternalServerError); + } + else if (element == "item-not-found") { + getPayloadInternal()->setCondition(Error::ItemNotFound); + } + else if (element == "jid-malformed") { + getPayloadInternal()->setCondition(Error::JIDMalformed); + } + else if (element == "not-acceptable") { + getPayloadInternal()->setCondition(Error::NotAcceptable); + } + else if (element == "not-allowed") { + getPayloadInternal()->setCondition(Error::NotAllowed); + } + else if (element == "not-authorized") { + getPayloadInternal()->setCondition(Error::NotAuthorized); + } + else if (element == "payment-required") { + getPayloadInternal()->setCondition(Error::PaymentRequired); + } + else if (element == "recipient-unavailable") { + getPayloadInternal()->setCondition(Error::RecipientUnavailable); + } + else if (element == "redirect") { + getPayloadInternal()->setCondition(Error::Redirect); + } + else if (element == "registration-required") { + getPayloadInternal()->setCondition(Error::RegistrationRequired); + } + else if (element == "remote-server-not-found") { + getPayloadInternal()->setCondition(Error::RemoteServerNotFound); + } + else if (element == "remote-server-timeout") { + getPayloadInternal()->setCondition(Error::RemoteServerTimeout); + } + else if (element == "resource-constraint") { + getPayloadInternal()->setCondition(Error::ResourceConstraint); + } + else if (element == "service-unavailable") { + getPayloadInternal()->setCondition(Error::ServiceUnavailable); + } + else if (element == "subscription-required") { + getPayloadInternal()->setCondition(Error::SubscriptionRequired); + } + else if (element == "unexpected-request") { + getPayloadInternal()->setCondition(Error::UnexpectedRequest); + } + else { + getPayloadInternal()->setCondition(Error::UndefinedCondition); + } + } +} + +void ErrorParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/ErrorParser.h b/Swiften/Parser/PayloadParsers/ErrorParser.h new file mode 100644 index 0000000..76db205 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParser.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_ErrorParser_H +#define SWIFTEN_ErrorParser_H + +#include "Swiften/Elements/Error.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class ErrorParser : public GenericPayloadParser<Error> { + public: + ErrorParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ErrorParserFactory.h b/Swiften/Parser/PayloadParsers/ErrorParserFactory.h new file mode 100644 index 0000000..36d1f55 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ErrorParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_ErrorParserFactory_H +#define SWIFTEN_ErrorParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" + +namespace Swift { + class ErrorParserFactory : public GenericPayloadParserFactory<ErrorParser> { + public: + ErrorParserFactory() : GenericPayloadParserFactory<ErrorParser>("error") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp new file mode 100644 index 0000000..fb2fea7 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -0,0 +1,46 @@ +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Parser/GenericPayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ErrorParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BodyParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PriorityParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StartSessionParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusShowParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RosterParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h" + +using namespace boost; + +namespace Swift { + +FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { + factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusShowParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new BodyParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new PriorityParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new ErrorParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SoftwareVersionParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new RosterParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoInfoParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new ResourceBindParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StartSessionParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelsCatalogParserFactory())); + foreach(shared_ptr<PayloadParserFactory> factory, factories_) { + addFactory(factory.get()); + } +} + +FullPayloadParserFactoryCollection::~FullPayloadParserFactoryCollection() { + foreach(shared_ptr<PayloadParserFactory> factory, factories_) { + removeFactory(factory.get()); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h new file mode 100644 index 0000000..3c383ec --- /dev/null +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_FULLPAYLOADPARSERFACTORYCOLLECTION_H +#define SWIFTEN_FULLPAYLOADPARSERFACTORYCOLLECTION_H + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/PayloadParserFactory.h" + +namespace Swift { + class FullPayloadParserFactoryCollection : public PayloadParserFactoryCollection { + public: + FullPayloadParserFactoryCollection(); + ~FullPayloadParserFactoryCollection(); + + private: + std::vector< boost::shared_ptr<PayloadParserFactory> > factories_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/Makefile.inc b/Swiften/Parser/PayloadParsers/Makefile.inc new file mode 100644 index 0000000..828dc5e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/Makefile.inc @@ -0,0 +1,15 @@ +SWIFTEN_SOURCES += \ + Swiften/Parser/PayloadParsers/BodyParser.cpp \ + Swiften/Parser/PayloadParsers/PriorityParser.cpp \ + Swiften/Parser/PayloadParsers/StatusParser.cpp \ + Swiften/Parser/PayloadParsers/StatusShowParser.cpp \ + Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp \ + Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp \ + Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp \ + Swiften/Parser/PayloadParsers/DiscoInfoParser.cpp \ + Swiften/Parser/PayloadParsers/ErrorParser.cpp \ + Swiften/Parser/PayloadParsers/RosterParser.cpp \ + Swiften/Parser/PayloadParsers/ResourceBindParser.cpp \ + Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp + +include Swiften/Parser/PayloadParsers/UnitTest/Makefile.inc diff --git a/Swiften/Parser/PayloadParsers/PriorityParser.cpp b/Swiften/Parser/PayloadParsers/PriorityParser.cpp new file mode 100644 index 0000000..3dcca51 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParser.cpp @@ -0,0 +1,25 @@ +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" + +#include <boost/lexical_cast.hpp> + +namespace Swift { + +PriorityParser::PriorityParser() : level_(0) { +} + +void PriorityParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void PriorityParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setPriority(boost::lexical_cast<int>(text_)); + } +} + +void PriorityParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/PriorityParser.h b/Swiften/Parser/PayloadParsers/PriorityParser.h new file mode 100644 index 0000000..7f3836f --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_PriorityParser_H +#define SWIFTEN_PriorityParser_H + +#include "Swiften/Elements/Priority.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class PriorityParser : public GenericPayloadParser<Priority> { + public: + PriorityParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/PriorityParserFactory.h b/Swiften/Parser/PayloadParsers/PriorityParserFactory.h new file mode 100644 index 0000000..5386326 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/PriorityParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_PriorityParserFactory_H +#define SWIFTEN_PriorityParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" + +namespace Swift { + class PriorityParserFactory : public GenericPayloadParserFactory<PriorityParser> { + public: + PriorityParserFactory() : GenericPayloadParserFactory<PriorityParser>("priority") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp b/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp new file mode 100644 index 0000000..c5ca787 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParser.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" + +namespace Swift { + +ResourceBindParser::ResourceBindParser() : level_(0), inJID_(false), inResource_(false) { +} + +void ResourceBindParser::handleStartElement(const String& element, const String&, const AttributeMap&) { + if (level_ == 1) { + text_ = ""; + if (element == "resource") { + inResource_ = true; + } + if (element == "jid") { + inJID_ = true; + } + } + ++level_; +} + +void ResourceBindParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 1) { + if (inJID_) { + getPayloadInternal()->setJID(JID(text_)); + } + else if (inResource_) { + getPayloadInternal()->setResource(text_); + } + } +} + +void ResourceBindParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParser.h b/Swiften/Parser/PayloadParsers/ResourceBindParser.h new file mode 100644 index 0000000..1341140 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParser.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_ResourceBindParser_H +#define SWIFTEN_ResourceBindParser_H + +#include "Swiften/Elements/ResourceBind.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class ResourceBindParser : public GenericPayloadParser<ResourceBind> { + public: + ResourceBindParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + bool inJID_; + bool inResource_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h b/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h new file mode 100644 index 0000000..54af9c9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/ResourceBindParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_ResourceBindParserFactory_H +#define SWIFTEN_ResourceBindParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" + +namespace Swift { + class ResourceBindParserFactory : public GenericPayloadParserFactory<ResourceBindParser> { + public: + ResourceBindParserFactory() : GenericPayloadParserFactory<ResourceBindParser>("bind", "urn:ietf:params:xml:ns:xmpp-bind") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/RosterParser.cpp b/Swiften/Parser/PayloadParsers/RosterParser.cpp new file mode 100644 index 0000000..0c4e99b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParser.cpp @@ -0,0 +1,66 @@ +#include "Swiften/Parser/PayloadParsers/RosterParser.h" + +namespace Swift { + +RosterParser::RosterParser() : level_(TopLevel) { +} + +void RosterParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level_ == PayloadLevel) { + if (element == "item") { + inItem_ = true; + currentItem_ = RosterItemPayload(); + + currentItem_.setJID(JID(attributes.getAttribute("jid"))); + currentItem_.setName(attributes.getAttribute("name")); + + String subscription = attributes.getAttribute("subscription"); + if (subscription == "both") { + currentItem_.setSubscription(RosterItemPayload::Both); + } + else if (subscription == "to") { + currentItem_.setSubscription(RosterItemPayload::To); + } + else if (subscription == "from") { + currentItem_.setSubscription(RosterItemPayload::From); + } + else if (subscription == "remove") { + currentItem_.setSubscription(RosterItemPayload::Remove); + } + else { + currentItem_.setSubscription(RosterItemPayload::None); + } + + if (attributes.getAttribute("ask") == "subscribe") { + currentItem_.setSubscriptionRequested(); + } + } + } + else if (level_ == ItemLevel) { + if (element == "group") { + currentText_ = ""; + } + } + ++level_; +} + +void RosterParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (inItem_) { + getPayloadInternal()->addItem(currentItem_); + inItem_ = false; + } + } + else if (level_ == ItemLevel) { + if (element == "group") { + currentItem_.addGroup(currentText_); + } + } +} + +void RosterParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/RosterParser.h b/Swiften/Parser/PayloadParsers/RosterParser.h new file mode 100644 index 0000000..bd8186a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParser.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_RosterParser_H +#define SWIFTEN_RosterParser_H + +#include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class RosterParser : public GenericPayloadParser<RosterPayload> { + public: + RosterParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + ItemLevel = 2 + }; + int level_; + bool inItem_; + RosterItemPayload currentItem_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/RosterParserFactory.h b/Swiften/Parser/PayloadParsers/RosterParserFactory.h new file mode 100644 index 0000000..f51b3ab --- /dev/null +++ b/Swiften/Parser/PayloadParsers/RosterParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_RosterParserFactory_H +#define SWIFTEN_RosterParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/RosterParser.h" + +namespace Swift { + class RosterParserFactory : public GenericPayloadParserFactory<RosterParser> { + public: + RosterParserFactory() : GenericPayloadParserFactory<RosterParser>("query", "jabber:iq:roster") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp b/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp new file mode 100644 index 0000000..7e65575 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParser.cpp @@ -0,0 +1,59 @@ +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" +#include "Swiften/Parser/SerializingParser.h" + +namespace Swift { + +SecurityLabelParser::SecurityLabelParser() : level_(TopLevel), labelParser_(0) { +} + +void SecurityLabelParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + if (level_ == DisplayMarkingOrLabelLevel) { + if (element == "displaymarking") { + currentText_ = ""; + getPayloadInternal()->setBackgroundColor(attributes.getAttribute("bgcolor")); + getPayloadInternal()->setForegroundColor(attributes.getAttribute("fgcolor")); + } + else if (element == "label" || element == "equivalentlabel") { + assert(!labelParser_); + labelParser_ = new SerializingParser(); + } + } + else if (level_ >= SecurityLabelLevel && labelParser_) { + labelParser_->handleStartElement(element, ns, attributes); + } +} + +void SecurityLabelParser::handleEndElement(const String& element, const String& ns) { + if (level_ == DisplayMarkingOrLabelLevel) { + if (element == "displaymarking") { + getPayloadInternal()->setDisplayMarking(currentText_); + } + else if (labelParser_) { + if (element == "label") { + getPayloadInternal()->setLabel(labelParser_->getResult()); + } + else { + getPayloadInternal()->addEquivalentLabel(labelParser_->getResult()); + } + delete labelParser_; + labelParser_ = 0; + } + } + else if (labelParser_ && level_ >= SecurityLabelLevel) { + labelParser_->handleEndElement(element, ns); + } + --level_; + +} + +void SecurityLabelParser::handleCharacterData(const String& data) { + if (labelParser_) { + labelParser_->handleCharacterData(data); + } + else { + currentText_ += data; + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParser.h b/Swiften/Parser/PayloadParsers/SecurityLabelParser.h new file mode 100644 index 0000000..70040d9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParser.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_SecurityLabelParser_H +#define SWIFTEN_SecurityLabelParser_H + +#include "Swiften/Elements/SecurityLabel.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SerializingParser; + + class SecurityLabelParser : public GenericPayloadParser<SecurityLabel> { + public: + SecurityLabelParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + DisplayMarkingOrLabelLevel = 2, + SecurityLabelLevel = 3 + }; + int level_; + SerializingParser* labelParser_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h b/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h new file mode 100644 index 0000000..0341fbb --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SecurityLabelParserFactory_H +#define SWIFTEN_SecurityLabelParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" + +namespace Swift { + class SecurityLabelParserFactory : public GenericPayloadParserFactory<SecurityLabelParser> { + public: + SecurityLabelParserFactory() : GenericPayloadParserFactory<SecurityLabelParser>("securitylabel", "urn:xmpp:sec-label:0") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp new file mode 100644 index 0000000..d571f0b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp @@ -0,0 +1,55 @@ +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" + +namespace Swift { + +SecurityLabelsCatalogParser::SecurityLabelsCatalogParser() : level_(TopLevel), labelParser_(0) { + labelParserFactory_ = new SecurityLabelParserFactory(); +} + +SecurityLabelsCatalogParser::~SecurityLabelsCatalogParser() { + delete labelParserFactory_; +} + +void SecurityLabelsCatalogParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + if (level_ == PayloadLevel) { + getPayloadInternal()->setTo(JID(attributes.getAttribute("to"))); + getPayloadInternal()->setName(attributes.getAttribute("name")); + getPayloadInternal()->setDescription(attributes.getAttribute("desc")); + } + else if (level_ == LabelLevel) { + assert(!labelParser_); + if (labelParserFactory_->canParse(element, ns, attributes)) { + labelParser_ = dynamic_cast<SecurityLabelParser*>(labelParserFactory_->createPayloadParser()); + assert(labelParser_); + } + } + + if (labelParser_) { + labelParser_->handleStartElement(element, ns, attributes); + } +} + +void SecurityLabelsCatalogParser::handleEndElement(const String& element, const String& ns) { + if (labelParser_) { + labelParser_->handleEndElement(element, ns); + } + if (level_ == LabelLevel && labelParser_) { + SecurityLabel* label = dynamic_cast<SecurityLabel*>(labelParser_->getPayload().get()); + assert(label); + getPayloadInternal()->addLabel(SecurityLabel(*label)); + delete labelParser_; + labelParser_ = 0; + } + --level_; +} + +void SecurityLabelsCatalogParser::handleCharacterData(const String& data) { + if (labelParser_) { + labelParser_->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h new file mode 100644 index 0000000..ec31235 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogParser_H +#define SWIFTEN_SecurityLabelsCatalogParser_H + +#include "Swiften/Elements/SecurityLabelsCatalog.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SecurityLabelParserFactory; + class SecurityLabelParser; + + class SecurityLabelsCatalogParser : public GenericPayloadParser<SecurityLabelsCatalog> { + public: + SecurityLabelsCatalogParser(); + ~SecurityLabelsCatalogParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + LabelLevel = 2 + }; + int level_; + SecurityLabelParserFactory* labelParserFactory_; + SecurityLabelParser* labelParser_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h new file mode 100644 index 0000000..99a310b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogParserFactory_H +#define SWIFTEN_SecurityLabelsCatalogParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" + +namespace Swift { + class SecurityLabelsCatalogParserFactory : public GenericPayloadParserFactory<SecurityLabelsCatalogParser> { + public: + SecurityLabelsCatalogParserFactory() : GenericPayloadParserFactory<SecurityLabelsCatalogParser>("catalog", "urn:xmpp:sec-label:catalog:0") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp new file mode 100644 index 0000000..dae9f94 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.cpp @@ -0,0 +1,32 @@ +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" + +namespace Swift { + +SoftwareVersionParser::SoftwareVersionParser() : level_(TopLevel) { +} + +void SoftwareVersionParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void SoftwareVersionParser::handleEndElement(const String& element, const String&) { + --level_; + if (level_ == PayloadLevel) { + if (element == "name") { + getPayloadInternal()->setName(currentText_); + } + else if (element == "version") { + getPayloadInternal()->setVersion(currentText_); + } + else if (element == "os") { + getPayloadInternal()->setOS(currentText_); + } + currentText_ = ""; + } +} + +void SoftwareVersionParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h new file mode 100644 index 0000000..bfffc90 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParser.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_SoftwareVersionParser_H +#define SWIFTEN_SoftwareVersionParser_H + +#include "Swiften/Elements/SoftwareVersion.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class SoftwareVersionParser : public GenericPayloadParser<SoftwareVersion> { + public: + SoftwareVersionParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1 + }; + int level_; + String currentText_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h b/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h new file mode 100644 index 0000000..cb33e0b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_SoftwareVersionParserFactory_H +#define SWIFTEN_SoftwareVersionParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" + +namespace Swift { + class SoftwareVersionParserFactory : public GenericPayloadParserFactory<SoftwareVersionParser> { + public: + SoftwareVersionParserFactory() : GenericPayloadParserFactory<SoftwareVersionParser>("query", "jabber:iq:version") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StartSessionParser.h b/Swiften/Parser/PayloadParsers/StartSessionParser.h new file mode 100644 index 0000000..059d036 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StartSessionParser.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_StartSessionParser_H +#define SWIFTEN_StartSessionParser_H + +#include "Swiften/Elements/StartSession.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StartSessionParser : public GenericPayloadParser<StartSession> { + public: + StartSessionParser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h b/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h new file mode 100644 index 0000000..5eed749 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StartSessionParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartSessionParserFactory_H +#define SWIFTEN_StartSessionParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StartSessionParser.h" + +namespace Swift { + class StartSessionParserFactory : public GenericPayloadParserFactory<StartSessionParser> { + public: + StartSessionParserFactory() : GenericPayloadParserFactory<StartSessionParser>("session", "urn:ietf:params:xml:ns:xmpp-session") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusParser.cpp b/Swiften/Parser/PayloadParsers/StatusParser.cpp new file mode 100644 index 0000000..c7771b9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParser.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Parser/PayloadParsers/StatusParser.h" + +namespace Swift { + +StatusParser::StatusParser() : level_(0) { +} + +void StatusParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void StatusParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + getPayloadInternal()->setText(text_); + } +} + +void StatusParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/StatusParser.h b/Swiften/Parser/PayloadParsers/StatusParser.h new file mode 100644 index 0000000..36ae094 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusParser_H +#define SWIFTEN_StatusParser_H + +#include "Swiften/Elements/Status.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StatusParser : public GenericPayloadParser<Status> { + public: + StatusParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusParserFactory.h b/Swiften/Parser/PayloadParsers/StatusParserFactory.h new file mode 100644 index 0000000..72af5a9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StatusParserFactory_H +#define SWIFTEN_StatusParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusParser.h" + +namespace Swift { + class StatusParserFactory : public GenericPayloadParserFactory<StatusParser> { + public: + StatusParserFactory() : GenericPayloadParserFactory<StatusParser>("status") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusShowParser.cpp b/Swiften/Parser/PayloadParsers/StatusShowParser.cpp new file mode 100644 index 0000000..c3719af --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParser.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" + +namespace Swift { + +StatusShowParser::StatusShowParser() : level_(0) { +} + +void StatusShowParser::handleStartElement(const String&, const String&, const AttributeMap&) { + ++level_; +} + +void StatusShowParser::handleEndElement(const String&, const String&) { + --level_; + if (level_ == 0) { + if (text_ == "away") { + getPayloadInternal()->setType(StatusShow::Away); + } + else if (text_ == "chat") { + getPayloadInternal()->setType(StatusShow::FFC); + } + else if (text_ == "xa") { + getPayloadInternal()->setType(StatusShow::XA); + } + else if (text_ == "dnd") { + getPayloadInternal()->setType(StatusShow::DND); + } + else { + getPayloadInternal()->setType(StatusShow::Online); + } + } +} + +void StatusShowParser::handleCharacterData(const String& data) { + text_ += data; +} + +} diff --git a/Swiften/Parser/PayloadParsers/StatusShowParser.h b/Swiften/Parser/PayloadParsers/StatusShowParser.h new file mode 100644 index 0000000..a8ddb09 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParser.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusShowParser_H +#define SWIFTEN_StatusShowParser_H + +#include "Swiften/Elements/StatusShow.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class StatusShowParser : public GenericPayloadParser<StatusShow> { + public: + StatusShowParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + int level_; + String text_; + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h b/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h new file mode 100644 index 0000000..2ddc190 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StatusShowParserFactory.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StatusShowParserFactory_H +#define SWIFTEN_StatusShowParserFactory_H + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" + +namespace Swift { + class StatusShowParserFactory : public GenericPayloadParserFactory<StatusShowParser> { + public: + StatusShowParserFactory() : GenericPayloadParserFactory<StatusShowParser>("show") {} + }; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp new file mode 100644 index 0000000..c747452 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp @@ -0,0 +1,29 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/BodyParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class BodyParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(BodyParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + BodyParserTest() {} + + void testParse() { + BodyParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<body>foo<baz>bar</baz>fum</body>")); + + Body* payload = dynamic_cast<Body*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("foobarfum"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BodyParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp new file mode 100644 index 0000000..5aed12f --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp @@ -0,0 +1,48 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class DiscoInfoParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(DiscoInfoParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoParserTest() {} + + void testParse() { + DiscoInfoParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns=\"http://jabber.org/protocol/disco#info\">" + "<identity name=\"Swift\" category=\"client\" type=\"pc\" xml:lang=\"en\"/>" + "<identity name=\"Vlug\" category=\"client\" type=\"pc\" xml:lang=\"nl\"/>" + "<feature var=\"foo-feature\"/>" + "<feature var=\"bar-feature\"/>" + "<feature var=\"baz-feature\"/>" + "</query>")); + + DiscoInfo* payload = dynamic_cast<DiscoInfo*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(payload->getIdentities().size())); + CPPUNIT_ASSERT_EQUAL(String("Swift"), payload->getIdentities()[0].getName()); + CPPUNIT_ASSERT_EQUAL(String("pc"), payload->getIdentities()[0].getType()); + CPPUNIT_ASSERT_EQUAL(String("client"), payload->getIdentities()[0].getCategory()); + CPPUNIT_ASSERT_EQUAL(String("en"), payload->getIdentities()[0].getLanguage()); + CPPUNIT_ASSERT_EQUAL(String("Vlug"), payload->getIdentities()[1].getName()); + CPPUNIT_ASSERT_EQUAL(String("pc"), payload->getIdentities()[1].getType()); + CPPUNIT_ASSERT_EQUAL(String("client"), payload->getIdentities()[1].getCategory()); + CPPUNIT_ASSERT_EQUAL(String("nl"), payload->getIdentities()[1].getLanguage()); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(payload->getFeatures().size())); + CPPUNIT_ASSERT_EQUAL(String("foo-feature"), payload->getFeatures()[0]); + CPPUNIT_ASSERT_EQUAL(String("bar-feature"), payload->getFeatures()[1]); + CPPUNIT_ASSERT_EQUAL(String("baz-feature"), payload->getFeatures()[2]); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp new file mode 100644 index 0000000..719702d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/ErrorParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class ErrorParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ErrorParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + ErrorParserTest() {} + + void testParse() { + ErrorParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<error type=\"modify\">" + "<bad-request xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>" + "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">boo</text>" + "</error>")); + + Error* payload = dynamic_cast<Error*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(Error::BadRequest, payload->getCondition()); + CPPUNIT_ASSERT_EQUAL(Error::Modify, payload->getType()); + CPPUNIT_ASSERT_EQUAL(String("boo"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ErrorParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/Makefile.inc b/Swiften/Parser/PayloadParsers/UnitTest/Makefile.inc new file mode 100644 index 0000000..f4d4cf9 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/Makefile.inc @@ -0,0 +1,12 @@ +UNITTEST_SOURCES += \ + Swiften/Parser/PayloadParsers/UnitTest/BodyParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp \ + Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h b/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h new file mode 100644 index 0000000..bac33bf --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h @@ -0,0 +1,10 @@ +#ifndef SWIFTEN_PayloadParserTester_H +#define SWIFTEN_PayloadParserTester_H + +#include "Swiften/Parser/UnitTest/ParserTester.h" + +namespace Swift { + typedef ParserTester<PayloadParser> PayloadParserTester; +} + +#endif diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp new file mode 100644 index 0000000..a186ebd --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp @@ -0,0 +1,29 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/PriorityParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class PriorityParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PriorityParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + PriorityParserTest() {} + + void testParse() { + PriorityParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<priority>-120</priority>")); + + Priority* payload = dynamic_cast<Priority*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(-120, payload->getPriority()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PriorityParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp new file mode 100644 index 0000000..67cb9cc --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp @@ -0,0 +1,40 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/ResourceBindParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class ResourceBindParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResourceBindParserTest); + CPPUNIT_TEST(testParse_JID); + CPPUNIT_TEST(testParse_Resource); + CPPUNIT_TEST_SUITE_END(); + + public: + ResourceBindParserTest() {} + + void testParse_JID() { + ResourceBindParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>somenode@example.com/someresource</jid></bind>")); + + ResourceBind* payload = dynamic_cast<ResourceBind*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(JID("somenode@example.com/someresource"), payload->getJID()); + } + + void testParse_Resource() { + ResourceBindParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>someresource</resource></bind>")); + + ResourceBind* payload = dynamic_cast<ResourceBind*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("someresource"), payload->getResource()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResourceBindParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp new file mode 100644 index 0000000..7f0fc64 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/RosterParserTest.cpp @@ -0,0 +1,51 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/RosterParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/XMLPayloadParser.h" + +using namespace Swift; + +class RosterParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + RosterParserTest() {} + + void testParse() { + RosterParser testling; + XMLPayloadParser parser(&testling); + parser.parse( + "<query xmlns='jabber:iq:roster'>" + " <item jid='foo@bar.com' name='Foo @ Bar' subscription='from' ask='subscribe'>" + " <group>Group 1</group>" + " <group>Group 2</group>" + " </item>" + " <item jid='baz@blo.com' name='Baz'/>" + "</query>"); + + RosterPayload* payload = dynamic_cast<RosterPayload*>(testling.getPayload().get()); + const RosterPayload::RosterItemPayloads& items = payload->getItems(); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size()); + + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), items[0].getJID()); + CPPUNIT_ASSERT_EQUAL(String("Foo @ Bar"), items[0].getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::From, items[0].getSubscription()); + CPPUNIT_ASSERT(items[0].getSubscriptionRequested()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items[0].getGroups().size()); + CPPUNIT_ASSERT_EQUAL(String("Group 1"), items[0].getGroups()[0]); + CPPUNIT_ASSERT_EQUAL(String("Group 2"), items[0].getGroups()[1]); + + CPPUNIT_ASSERT_EQUAL(JID("baz@blo.com"), items[1].getJID()); + CPPUNIT_ASSERT_EQUAL(String("Baz"), items[1].getName()); + CPPUNIT_ASSERT_EQUAL(RosterItemPayload::None, items[1].getSubscription()); + CPPUNIT_ASSERT(!items[1].getSubscriptionRequested()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), items[1].getGroups().size()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RosterParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp new file mode 100644 index 0000000..5da3fbd --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp @@ -0,0 +1,46 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SecurityLabelParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class SecurityLabelParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelParserTest() {} + + void testParse() { + SecurityLabelParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking fgcolor=\"black\" bgcolor=\"red\">SECRET</displaymarking>" + "<label>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>" + "</label>" + "<equivalentlabel>" + "<icismlabel xmlns=\"http://example.gov/IC-ISM/0\" classification=\"S\" ownerProducer=\"USA\" disseminationControls=\"FOUO\"/>" + "</equivalentlabel>" + "<equivalentlabel>" + "<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>" + "</equivalentlabel>" + "</securitylabel>")); + + SecurityLabel* payload = dynamic_cast<SecurityLabel*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("SECRET"), payload->getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("black"), payload->getForegroundColor()); + CPPUNIT_ASSERT_EQUAL(String("red"), payload->getBackgroundColor()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"), payload->getLabel()); + CPPUNIT_ASSERT_EQUAL(String("<icismlabel classification=\"S\" disseminationControls=\"FOUO\" ownerProducer=\"USA\" xmlns=\"http://example.gov/IC-ISM/0\"/>"), payload->getEquivalentLabels()[0]); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>"), payload->getEquivalentLabels()[1]); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp new file mode 100644 index 0000000..0021c0d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp @@ -0,0 +1,46 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class SecurityLabelsCatalogParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelsCatalogParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelsCatalogParserTest() {} + + void testParse() { + SecurityLabelsCatalogParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<catalog desc=\"an example set of labels\" name=\"Default\" to=\"example.com\" xmlns=\"urn:xmpp:sec-label:catalog:0\">" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"red\" fgcolor=\"black\">SECRET</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel></label>" + "</securitylabel>" + "<securitylabel xmlns=\"urn:xmpp:sec-label:0\">" + "<displaymarking bgcolor=\"navy\" fgcolor=\"black\">CONFIDENTIAL</displaymarking>" + "<label><esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel></label>" + "</securitylabel>" + "</catalog>")); + + SecurityLabelsCatalog* payload = dynamic_cast<SecurityLabelsCatalog*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("Default"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(String("an example set of labels"), payload->getDescription()); + CPPUNIT_ASSERT_EQUAL(JID("example.com"), payload->getTo()); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(payload->getLabels().size())); + CPPUNIT_ASSERT_EQUAL(String("SECRET"), payload->getLabels()[0].getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"), payload->getLabels()[0].getLabel()); + CPPUNIT_ASSERT_EQUAL(String("CONFIDENTIAL"), payload->getLabels()[1].getDisplayMarking()); + CPPUNIT_ASSERT_EQUAL(String("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel>"), payload->getLabels()[1].getLabel()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelsCatalogParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp new file mode 100644 index 0000000..0ff1d00 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp @@ -0,0 +1,36 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/SoftwareVersionParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class SoftwareVersionParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SoftwareVersionParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + SoftwareVersionParserTest() {} + + void testParse() { + SoftwareVersionParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<query xmlns=\"jabber:iq:version\">" + "<name>myclient</name>" + "<version>1.0</version>" + "<os>Mac OS X</os>" + "</query>")); + + SoftwareVersion* payload = dynamic_cast<SoftwareVersion*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("myclient"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(String("1.0"), payload->getVersion()); + CPPUNIT_ASSERT_EQUAL(String("Mac OS X"), payload->getOS()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SoftwareVersionParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp new file mode 100644 index 0000000..f1fa7b1 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StatusParserTest.cpp @@ -0,0 +1,29 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/StatusParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class StatusParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusParserTest() {} + + void testParse() { + StatusParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<status>foo<baz>bar</baz>fum</status>")); + + Status* payload = dynamic_cast<Status*>(testling.getPayload().get()); + CPPUNIT_ASSERT_EQUAL(String("foobarfum"), payload->getText()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp new file mode 100644 index 0000000..d89fdc5 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp @@ -0,0 +1,73 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/StatusShowParser.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" + +using namespace Swift; + +class StatusShowParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusShowParserTest); + CPPUNIT_TEST(testParse_Invalid); + CPPUNIT_TEST(testParse_Away); + CPPUNIT_TEST(testParse_FFC); + CPPUNIT_TEST(testParse_XA); + CPPUNIT_TEST(testParse_DND); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusShowParserTest() {} + + void testParse_Invalid() { + StatusShowParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<show>invalid</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(testling.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::Online == payload->getType()); + } + + void testParse_Away() { + StatusShowParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<show>away</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(testling.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::Away == payload->getType()); + } + + void testParse_FFC() { + StatusShowParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<show>chat</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(testling.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::FFC == payload->getType()); + } + + void testParse_XA() { + StatusShowParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<show>xa</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(testling.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::XA == payload->getType()); + } + + void testParse_DND() { + StatusShowParser testling; + PayloadParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<show>dnd</show>")); + + StatusShow* payload = dynamic_cast<StatusShow*>(testling.getPayload().get()); + CPPUNIT_ASSERT(StatusShow::DND == payload->getType()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusShowParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/XMLPayloadParser.h b/Swiften/Parser/PayloadParsers/UnitTest/XMLPayloadParser.h new file mode 100644 index 0000000..2b893c4 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/XMLPayloadParser.h @@ -0,0 +1,42 @@ +#ifndef SWIFTEN_XMLPayloadParser_H +#define SWIFTEN_XMLPayloadParser_H + +#include "Swiften/Parser/PayloadParser.h" +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/XMLParser.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" + +namespace Swift { + class XMLPayloadParser : public XMLParserClient { + public: + XMLPayloadParser(PayloadParser* payloadParser) : + payloadParser_(payloadParser) { + xmlParser_ = PlatformXMLParserFactory().createXMLParser(this); + } + + ~XMLPayloadParser() { + delete xmlParser_; + } + + bool parse(const String& data) { + return xmlParser_->parse(data); + } + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + payloadParser_->handleStartElement(element, ns, attributes); + } + + virtual void handleEndElement(const String& element, const String& ns) { + payloadParser_->handleEndElement(element, ns); + } + + virtual void handleCharacterData(const String& data) { + payloadParser_->handleCharacterData(data); + } + + private: + XMLParser* xmlParser_; + PayloadParser* payloadParser_; + }; +} +#endif diff --git a/Swiften/Parser/PlatformXMLParserFactory.cpp b/Swiften/Parser/PlatformXMLParserFactory.cpp new file mode 100644 index 0000000..14557d1 --- /dev/null +++ b/Swiften/Parser/PlatformXMLParserFactory.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Parser/PlatformXMLParserFactory.h" + +#include <cassert> + +#ifdef HAVE_SWIFTEN_CONFIG_H +#include "Swiften/config.h" +#endif +#ifdef HAVE_LIBXML +#include "Swiften/Parser/LibXMLParser.h" +#else +#include "Swiften/Parser/ExpatParser.h" +#endif + + +namespace Swift { + +PlatformXMLParserFactory::PlatformXMLParserFactory() { +} + +XMLParser* PlatformXMLParserFactory::createXMLParser(XMLParserClient* client) { +#ifdef HAVE_LIBXML + return new LibXMLParser(client); +#else + return new ExpatParser(client); +#endif +} + +} diff --git a/Swiften/Parser/PlatformXMLParserFactory.h b/Swiften/Parser/PlatformXMLParserFactory.h new file mode 100644 index 0000000..28b1657 --- /dev/null +++ b/Swiften/Parser/PlatformXMLParserFactory.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_PlatformXMLParserFactory_H +#define SWIFTEN_PlatformXMLParserFactory_H + +#include "Swiften/Parser/XMLParserFactory.h" + +namespace Swift { + class PlatformXMLParserFactory : public XMLParserFactory { + public: + PlatformXMLParserFactory(); + + virtual XMLParser* createXMLParser(XMLParserClient*); + }; +} + +#endif diff --git a/Swiften/Parser/PresenceParser.cpp b/Swiften/Parser/PresenceParser.cpp new file mode 100644 index 0000000..72cdd4c --- /dev/null +++ b/Swiften/Parser/PresenceParser.cpp @@ -0,0 +1,45 @@ +#include <iostream> + +#include "Swiften/Parser/PresenceParser.h" + +namespace Swift { + +PresenceParser::PresenceParser(PayloadParserFactoryCollection* factories) : + GenericStanzaParser<Presence>(factories) { +} + +void PresenceParser::handleStanzaAttributes(const AttributeMap& attributes) { + AttributeMap::const_iterator type = attributes.find("type"); + if (type != attributes.end()) { + if (type->second == "unavailable") { + getStanzaGeneric()->setType(Presence::Unavailable); + } + else if (type->second == "probe") { + getStanzaGeneric()->setType(Presence::Probe); + } + else if (type->second == "subscribe") { + getStanzaGeneric()->setType(Presence::Subscribe); + } + else if (type->second == "subscribed") { + getStanzaGeneric()->setType(Presence::Subscribed); + } + else if (type->second == "unsubscribe") { + getStanzaGeneric()->setType(Presence::Unsubscribe); + } + else if (type->second == "unsubscribed") { + getStanzaGeneric()->setType(Presence::Unsubscribed); + } + else if (type->second == "error") { + getStanzaGeneric()->setType(Presence::Error); + } + else { + std::cerr << "Unknown Presence type: " << type->second << std::endl; + getStanzaGeneric()->setType(Presence::Available); + } + } + else { + getStanzaGeneric()->setType(Presence::Available); + } +} + +} diff --git a/Swiften/Parser/PresenceParser.h b/Swiften/Parser/PresenceParser.h new file mode 100644 index 0000000..b3ba445 --- /dev/null +++ b/Swiften/Parser/PresenceParser.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_PresenceParser_H +#define SWIFTEN_PresenceParser_H + +#include "Swiften/Parser/GenericStanzaParser.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class PresenceParser : public GenericStanzaParser<Presence> { + public: + PresenceParser(PayloadParserFactoryCollection* factories); + + private: + virtual void handleStanzaAttributes(const AttributeMap&); + }; +} + +#endif diff --git a/Swiften/Parser/SerializingParser.cpp b/Swiften/Parser/SerializingParser.cpp new file mode 100644 index 0000000..f69e732 --- /dev/null +++ b/Swiften/Parser/SerializingParser.cpp @@ -0,0 +1,41 @@ +#include "Swiften/Parser/SerializingParser.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Base/foreach.h" +#include <iostream> + +namespace Swift { + +SerializingParser::SerializingParser() { +} + +void SerializingParser::handleStartElement(const String& tag, const String& ns, const AttributeMap& attributes) { + boost::shared_ptr<XMLElement> element(new XMLElement(tag, ns)); + for (AttributeMap::const_iterator i = attributes.begin(); i != attributes.end(); ++i) { + element->setAttribute((*i).first, (*i).second); + } + + if (elementStack_.empty()) { + rootElement_ = element; + } + else { + (*(elementStack_.end() - 1))->addNode(element); + } + elementStack_.push_back(element); +} + +void SerializingParser::handleEndElement(const String&, const String&) { + assert(!elementStack_.empty()); + elementStack_.pop_back(); +} + +void SerializingParser::handleCharacterData(const String& data) { + if (!elementStack_.empty()) { + (*(elementStack_.end()-1))->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(data))); + } +} + +String SerializingParser::getResult() const { + return (rootElement_ ? rootElement_->serialize() : ""); +} + +} diff --git a/Swiften/Parser/SerializingParser.h b/Swiften/Parser/SerializingParser.h new file mode 100644 index 0000000..b1d4575 --- /dev/null +++ b/Swiften/Parser/SerializingParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_SerializingParser_H +#define SWIFTEN_SerializingParser_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/AttributeMap.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class SerializingParser { + public: + SerializingParser(); + + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + String getResult() const; + + private: + std::vector< boost::shared_ptr<XMLElement> > elementStack_; + boost::shared_ptr<XMLElement> rootElement_; + }; +} + +#endif diff --git a/Swiften/Parser/StanzaParser.cpp b/Swiften/Parser/StanzaParser.cpp new file mode 100644 index 0000000..952265c --- /dev/null +++ b/Swiften/Parser/StanzaParser.cpp @@ -0,0 +1,78 @@ +#include "Swiften/Parser/StanzaParser.h" + +#include <iostream> +#include <cassert> + +#include "Swiften/Parser/PayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnknownPayloadParser.h" + +namespace Swift { + +StanzaParser::StanzaParser(PayloadParserFactoryCollection* factories) : + currentDepth_(0), factories_(factories) { +} + +StanzaParser::~StanzaParser() { +} + +void StanzaParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (inStanza()) { + if (!inPayload()) { + assert(!currentPayloadParser_.get()); + PayloadParserFactory* payloadParserFactory = factories_->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser_.reset(payloadParserFactory->createPayloadParser()); + } + else { + currentPayloadParser_.reset(new UnknownPayloadParser()); + } + } + assert(currentPayloadParser_.get()); + currentPayloadParser_->handleStartElement(element, ns, attributes); + } + else { + AttributeMap::const_iterator from = attributes.find("from"); + if (from != attributes.end()) { + getStanza()->setFrom(JID(from->second)); + } + AttributeMap::const_iterator to = attributes.find("to"); + if (to != attributes.end()) { + getStanza()->setTo(JID(to->second)); + } + AttributeMap::const_iterator id = attributes.find("id"); + if (id != attributes.end()) { + getStanza()->setID(id->second); + } + handleStanzaAttributes(attributes); + } + ++currentDepth_; +} + +void StanzaParser::handleEndElement(const String& element, const String& ns) { + assert(inStanza()); + if (inPayload()) { + assert(currentPayloadParser_.get()); + currentPayloadParser_->handleEndElement(element, ns); + --currentDepth_; + if (!inPayload()) { + boost::shared_ptr<Payload> payload(currentPayloadParser_->getPayload()); + if (payload) { + getStanza()->addPayload(payload); + } + currentPayloadParser_.reset(); + } + } + else { + --currentDepth_; + } +} + +void StanzaParser::handleCharacterData(const String& data) { + if (currentPayloadParser_.get()) { + currentPayloadParser_->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/StanzaParser.h b/Swiften/Parser/StanzaParser.h new file mode 100644 index 0000000..c6cf753 --- /dev/null +++ b/Swiften/Parser/StanzaParser.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_StanzaParser_H +#define SWIFTEN_StanzaParser_H + +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class PayloadParser; + class PayloadParserFactoryCollection; + + class StanzaParser : public ElementParser, public boost::noncopyable { + public: + StanzaParser(PayloadParserFactoryCollection* factories); + ~StanzaParser(); + + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + virtual boost::shared_ptr<Element> getElement() const = 0; + virtual void handleStanzaAttributes(const AttributeMap&) {} + + virtual boost::shared_ptr<Stanza> getStanza() const { + return boost::dynamic_pointer_cast<Stanza>(getElement()); + } + + private: + bool inPayload() const { + return currentDepth_ > 1; + } + + bool inStanza() const { + return currentDepth_ > 0; + } + + + private: + int currentDepth_; + PayloadParserFactoryCollection* factories_; + std::auto_ptr<PayloadParser> currentPayloadParser_; + }; +} + +#endif diff --git a/Swiften/Parser/StartTLSFailureParser.h b/Swiften/Parser/StartTLSFailureParser.h new file mode 100644 index 0000000..c955ca0 --- /dev/null +++ b/Swiften/Parser/StartTLSFailureParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSFailureParser_H +#define SWIFTEN_StartTLSFailureParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StartTLSFailure.h" + +namespace Swift { + class StartTLSFailureParser : public GenericElementParser<StartTLSFailure> { + public: + StartTLSFailureParser() : GenericElementParser<StartTLSFailure>() {} + }; +} + +#endif diff --git a/Swiften/Parser/StartTLSParser.h b/Swiften/Parser/StartTLSParser.h new file mode 100644 index 0000000..afacec2 --- /dev/null +++ b/Swiften/Parser/StartTLSParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_StartTLSParser_H +#define SWIFTEN_StartTLSParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StartTLSRequest.h" + +namespace Swift { + class StartTLSParser : public GenericElementParser<StartTLSRequest> { + public: + StartTLSParser() : GenericElementParser<StartTLSRequest>() {} + }; +} + +#endif diff --git a/Swiften/Parser/StreamFeaturesParser.cpp b/Swiften/Parser/StreamFeaturesParser.cpp new file mode 100644 index 0000000..5072e7c --- /dev/null +++ b/Swiften/Parser/StreamFeaturesParser.cpp @@ -0,0 +1,61 @@ +#include "Swiften/Parser/StreamFeaturesParser.h" + +namespace Swift { + +StreamFeaturesParser::StreamFeaturesParser() : GenericElementParser<StreamFeatures>(), currentDepth_(0), inMechanisms_(false), inMechanism_(false), inCompression_(false), inCompressionMethod_(false) { +} + +void StreamFeaturesParser::handleStartElement(const String& element, const String& ns, const AttributeMap&) { + if (currentDepth_ == 1) { + if (element == "starttls" && ns == "urn:ietf:params:xml:ns:xmpp-tls") { + getElementGeneric()->setHasStartTLS(); + } + else if (element == "session" && ns == "urn:ietf:params:xml:ns:xmpp-session") { + getElementGeneric()->setHasSession(); + } + else if (element == "bind" && ns == "urn:ietf:params:xml:ns:xmpp-bind") { + getElementGeneric()->setHasResourceBind(); + } + else if (element == "mechanisms" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + inMechanisms_ = true; + } + else if (element == "compression" && ns == "http://jabber.org/features/compress") { + inCompression_ = true; + } + } + else if (currentDepth_ == 2) { + if (inCompression_ && element == "method") { + inCompressionMethod_ = true; + currentText_ = ""; + } + else if (inMechanisms_ && element == "mechanism") { + inMechanism_ = true; + currentText_ = ""; + } + } + ++currentDepth_; +} + +void StreamFeaturesParser::handleEndElement(const String&, const String&) { + --currentDepth_; + if (currentDepth_ == 1) { + inCompression_ = false; + inMechanisms_ = false; + } + else if (currentDepth_ == 2) { + if (inCompressionMethod_) { + getElementGeneric()->addCompressionMethod(currentText_); + inCompressionMethod_ = false; + } + else if (inMechanism_) { + getElementGeneric()->addAuthenticationMechanism(currentText_); + inMechanism_ = false; + } + } +} + +void StreamFeaturesParser::handleCharacterData(const String& data) { + currentText_ += data; +} + +} diff --git a/Swiften/Parser/StreamFeaturesParser.h b/Swiften/Parser/StreamFeaturesParser.h new file mode 100644 index 0000000..184a7e6 --- /dev/null +++ b/Swiften/Parser/StreamFeaturesParser.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_STREAMFEATURESPARSER_H +#define SWIFTEN_STREAMFEATURESPARSER_H + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/StreamFeatures.h" + +namespace Swift { + class StreamFeaturesParser : public GenericElementParser<StreamFeatures> { + public: + StreamFeaturesParser(); + + private: + void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes); + void handleEndElement(const String& element, const String& ns); + void handleCharacterData(const String& data); + + private: + int currentDepth_; + String currentText_; + bool inMechanisms_; + bool inMechanism_; + bool inCompression_; + bool inCompressionMethod_; + }; +} + +#endif diff --git a/Swiften/Parser/TLSProceedParser.h b/Swiften/Parser/TLSProceedParser.h new file mode 100644 index 0000000..2ad5438 --- /dev/null +++ b/Swiften/Parser/TLSProceedParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_TLSProceedParser_H +#define SWIFTEN_TLSProceedParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/TLSProceed.h" + +namespace Swift { + class TLSProceedParser : public GenericElementParser<TLSProceed> { + public: + TLSProceedParser() : GenericElementParser<TLSProceed>() {} + }; +} + +#endif diff --git a/Swiften/Parser/UnitTest/ElementParserTester.h b/Swiften/Parser/UnitTest/ElementParserTester.h new file mode 100644 index 0000000..4d84c44 --- /dev/null +++ b/Swiften/Parser/UnitTest/ElementParserTester.h @@ -0,0 +1,10 @@ +#ifndef SWIFTEN_ElementParserTester_H +#define SWIFTEN_ElementParserTester_H + +#include "Swiften/Parser/UnitTest/ParserTester.h" + +namespace Swift { + typedef ParserTester<ElementParser> ElementParserTester; +} + +#endif diff --git a/Swiften/Parser/UnitTest/IQParserTest.cpp b/Swiften/Parser/UnitTest/IQParserTest.cpp new file mode 100644 index 0000000..22b0adc --- /dev/null +++ b/Swiften/Parser/UnitTest/IQParserTest.cpp @@ -0,0 +1,70 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/IQParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class IQParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(IQParserTest); + CPPUNIT_TEST(testParse_Set); + CPPUNIT_TEST(testParse_Get); + CPPUNIT_TEST(testParse_Result); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + IQParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Set() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"set\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Set, testling.getStanzaGeneric()->getType()); + } + + void testParse_Get() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"get\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Get, testling.getStanzaGeneric()->getType()); + } + + void testParse_Result() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"result\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Result, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + IQParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<iq type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(IQ::Error, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IQParserTest); diff --git a/Swiften/Parser/UnitTest/Makefile.inc b/Swiften/Parser/UnitTest/Makefile.inc new file mode 100644 index 0000000..4f9c59e --- /dev/null +++ b/Swiften/Parser/UnitTest/Makefile.inc @@ -0,0 +1,9 @@ +UNITTEST_SOURCES += \ + Swiften/Parser/UnitTest/XMPPParserTest.cpp \ + Swiften/Parser/UnitTest/StanzaParserTest.cpp \ + Swiften/Parser/UnitTest/MessageParserTest.cpp \ + Swiften/Parser/UnitTest/PresenceParserTest.cpp \ + Swiften/Parser/UnitTest/IQParserTest.cpp \ + Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp \ + Swiften/Parser/UnitTest/SerializingParserTest.cpp \ + Swiften/Parser/UnitTest/XMLParserTest.cpp diff --git a/Swiften/Parser/UnitTest/MessageParserTest.cpp b/Swiften/Parser/UnitTest/MessageParserTest.cpp new file mode 100644 index 0000000..61a8d20 --- /dev/null +++ b/Swiften/Parser/UnitTest/MessageParserTest.cpp @@ -0,0 +1,80 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/MessageParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class MessageParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(MessageParserTest); + CPPUNIT_TEST(testParse_Normal); + CPPUNIT_TEST(testParse_Chat); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST(testParse_Groupchat); + CPPUNIT_TEST(testParse_Headline); + CPPUNIT_TEST_SUITE_END(); + + public: + MessageParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Chat() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"chat\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Chat, testling.getStanzaGeneric()->getType()); + } + + void testParse_Groupchat() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"groupchat\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Groupchat, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Error, testling.getStanzaGeneric()->getType()); + } + + void testParse_Headline() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message type=\"headline\"/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Headline, testling.getStanzaGeneric()->getType()); + } + + void testParse_Normal() { + MessageParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<message/>")); + + CPPUNIT_ASSERT_EQUAL(Message::Normal, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MessageParserTest); diff --git a/Swiften/Parser/UnitTest/ParserTester.h b/Swiften/Parser/UnitTest/ParserTester.h new file mode 100644 index 0000000..7aacc8e --- /dev/null +++ b/Swiften/Parser/UnitTest/ParserTester.h @@ -0,0 +1,44 @@ +#ifndef SWIFTEN_ParserTester_H +#define SWIFTEN_ParserTester_H + +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + class XMLParser; + + template<typename ParserType> + class ParserTester : public XMLParserClient { + public: + ParserTester(ParserType* parser) : parser_(parser) { + xmlParser_ = PlatformXMLParserFactory().createXMLParser(this); + } + + ~ParserTester() { + delete xmlParser_; + } + + bool parse(const String& data) { + return xmlParser_->parse(data); + } + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + parser_->handleStartElement(element, ns, attributes); + } + + virtual void handleEndElement(const String& element, const String& ns) { + parser_->handleEndElement(element, ns); + } + + virtual void handleCharacterData(const String& data) { + parser_->handleCharacterData(data); + } + + private: + XMLParser* xmlParser_; + ParserType* parser_; + }; +} + +#endif diff --git a/Swiften/Parser/UnitTest/PresenceParserTest.cpp b/Swiften/Parser/UnitTest/PresenceParserTest.cpp new file mode 100644 index 0000000..5305161 --- /dev/null +++ b/Swiften/Parser/UnitTest/PresenceParserTest.cpp @@ -0,0 +1,110 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PresenceParser.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class PresenceParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PresenceParserTest); + CPPUNIT_TEST(testParse_Available); + CPPUNIT_TEST(testParse_Unavailable); + CPPUNIT_TEST(testParse_Subscribe); + CPPUNIT_TEST(testParse_Subscribed); + CPPUNIT_TEST(testParse_Unsubscribe); + CPPUNIT_TEST(testParse_Unsubscribed); + CPPUNIT_TEST(testParse_Probe); + CPPUNIT_TEST(testParse_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + PresenceParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + } + + void tearDown() { + delete factoryCollection_; + } + + void testParse_Available() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Available, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unavailable() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unavailable\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unavailable, testling.getStanzaGeneric()->getType()); + } + + void testParse_Probe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"probe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Probe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Subscribe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"subscribe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Subscribe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Subscribed() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"subscribed\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Subscribed, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unsubscribe() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unsubscribe\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unsubscribe, testling.getStanzaGeneric()->getType()); + } + + void testParse_Unsubscribed() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"unsubscribed\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Unsubscribed, testling.getStanzaGeneric()->getType()); + } + + void testParse_Error() { + PresenceParser testling(factoryCollection_); + StanzaParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse("<presence type=\"error\"/>")); + + CPPUNIT_ASSERT_EQUAL(Presence::Error, testling.getStanzaGeneric()->getType()); + } + + private: + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PresenceParserTest); diff --git a/Swiften/Parser/UnitTest/SerializingParserTest.cpp b/Swiften/Parser/UnitTest/SerializingParserTest.cpp new file mode 100644 index 0000000..e08a3d0 --- /dev/null +++ b/Swiften/Parser/UnitTest/SerializingParserTest.cpp @@ -0,0 +1,58 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/SerializingParser.h" +#include "Swiften/Parser/UnitTest/StanzaParserTester.h" + +using namespace Swift; + +class SerializingParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SerializingParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_Empty); + CPPUNIT_TEST(testParse_ToplevelCharacterData); + CPPUNIT_TEST_SUITE_END(); + + public: + SerializingParserTest() {} + + void testParse() { + SerializingParser testling; + ParserTester<SerializingParser> parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<message type=\"chat\" to=\"me@foo.com\">" + "<body>Hello<&World</body>" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" + "foo<b>bar</b>baz" + "</html>" + "</message>")); + + CPPUNIT_ASSERT_EQUAL(String( + "<message to=\"me@foo.com\" type=\"chat\">" + "<body>Hello<&World</body>" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">foo<b xmlns=\"http://www.w3.org/1999/xhtml\">bar</b>baz</html>" + "</message>"), testling.getResult()); + } + + void testParse_Empty() { + SerializingParser testling; + + CPPUNIT_ASSERT_EQUAL(String(""), testling.getResult()); + } + + void testParse_ToplevelCharacterData() { + SerializingParser testling; + + AttributeMap attributes; + testling.handleCharacterData("foo"); + testling.handleStartElement("message", "", attributes); + testling.handleEndElement("message", ""); + testling.handleCharacterData("bar"); + + CPPUNIT_ASSERT_EQUAL(String("<message/>"), testling.getResult()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SerializingParserTest); diff --git a/Swiften/Parser/UnitTest/StanzaParserTest.cpp b/Swiften/Parser/UnitTest/StanzaParserTest.cpp new file mode 100644 index 0000000..3cb1879 --- /dev/null +++ b/Swiften/Parser/UnitTest/StanzaParserTest.cpp @@ -0,0 +1,209 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/StanzaParser.h" +#include "Swiften/Parser/GenericPayloadParser.h" +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Elements/Payload.h" + +using namespace Swift; + +class StanzaParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StanzaParserTest); + CPPUNIT_TEST(testHandleEndElement_OnePayload); + CPPUNIT_TEST(testHandleEndElement_MultiplePayloads); + CPPUNIT_TEST(testHandleEndElement_StrayCharacterData); + CPPUNIT_TEST(testHandleEndElement_UnknownPayload); + CPPUNIT_TEST(testHandleParse_BasicAttributes); + CPPUNIT_TEST_SUITE_END(); + + public: + StanzaParserTest() {} + + void setUp() { + factoryCollection_ = new PayloadParserFactoryCollection(); + factoryCollection_->addFactory(&factory1_); + factoryCollection_->addFactory(&factory2_); + } + + void tearDown() { + delete factoryCollection_; + } + + void testHandleEndElement_OnePayload() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + attributes["foo"] = "fum"; + attributes["bar"] = "baz"; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleStartElement("child", "", attributes); + testling.handleEndElement("child", ""); + testling.handleEndElement("mypayload1", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()->hasChild); + } + + void testHandleEndElement_MultiplePayloads() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleEndElement_StrayCharacterData() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleCharacterData("bla"); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleEndElement_UnknownPayload() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + testling.handleStartElement("mystanza", "", attributes); + testling.handleStartElement("mypayload1", "", attributes); + testling.handleEndElement("mypayload1", ""); + testling.handleStartElement("unknown-payload", "", attributes); + testling.handleStartElement("unknown-payload-child", "", attributes); + testling.handleEndElement("unknown-payload-child", ""); + testling.handleEndElement("unknown-payload", ""); + testling.handleStartElement("mypayload2", "", attributes); + testling.handleEndElement("mypayload2", ""); + testling.handleEndElement("mystanza", ""); + + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()); + CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>()); + } + + void testHandleParse_BasicAttributes() { + MyStanzaParser testling(factoryCollection_); + + AttributeMap attributes; + attributes["to"] = "foo@example.com/blo"; + attributes["from"] = "bar@example.com/baz"; + attributes["id"] = "id-123"; + testling.handleStartElement("mystanza", "", attributes); + testling.handleEndElement("mypayload1", ""); + + CPPUNIT_ASSERT_EQUAL(JID("foo@example.com/blo"), testling.getStanza()->getTo()); + CPPUNIT_ASSERT_EQUAL(JID("bar@example.com/baz"), testling.getStanza()->getFrom()); + CPPUNIT_ASSERT_EQUAL(String("id-123"), testling.getStanza()->getID()); + } + + private: + class MyPayload1 : public Payload + { + public: + MyPayload1() : hasChild(false) {} + + bool hasChild; + }; + + class MyPayload1Parser : public GenericPayloadParser<MyPayload1> + { + public: + MyPayload1Parser() {} + + virtual void handleStartElement(const String& element, const String&, const AttributeMap&) { + if (element != "mypayload1") { + getPayloadInternal()->hasChild = true; + } + } + + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; + + class MyPayload1ParserFactory : public PayloadParserFactory + { + public: + MyPayload1ParserFactory() {} + + PayloadParser* createPayloadParser() { return new MyPayload1Parser(); } + + bool canParse(const String& element, const String&, const AttributeMap&) const { + return element == "mypayload1"; + } + }; + + class MyPayload2 : public Payload + { + public: + MyPayload2() {} + }; + + class MyPayload2Parser : public GenericPayloadParser<MyPayload2> + { + public: + MyPayload2Parser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + }; + + + class MyPayload2ParserFactory : public PayloadParserFactory + { + public: + MyPayload2ParserFactory() {} + + PayloadParser* createPayloadParser() { return new MyPayload2Parser(); } + bool canParse(const String& element, const String&, const AttributeMap&) const { + return element == "mypayload2"; + } + }; + + class MyStanza : public Stanza + { + public: + MyStanza() {} + }; + + class MyStanzaParser : public StanzaParser + { + public: + MyStanzaParser(PayloadParserFactoryCollection* collection) : StanzaParser(collection) + { + stanza_ = boost::shared_ptr<MyStanza>(new MyStanza()); + } + + virtual boost::shared_ptr<Element> getElement() const { + return stanza_; + } + + private: + boost::shared_ptr<MyStanza> stanza_; + }; + + MyPayload1ParserFactory factory1_; + MyPayload2ParserFactory factory2_; + PayloadParserFactoryCollection* factoryCollection_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StanzaParserTest); diff --git a/Swiften/Parser/UnitTest/StanzaParserTester.h b/Swiften/Parser/UnitTest/StanzaParserTester.h new file mode 100644 index 0000000..cbd484f --- /dev/null +++ b/Swiften/Parser/UnitTest/StanzaParserTester.h @@ -0,0 +1,11 @@ +#ifndef SWIFTEN_StanzaParserTester_H +#define SWIFTEN_StanzaParserTester_H + +#include "Swiften/Parser/StanzaParser.h" +#include "Swiften/Parser/UnitTest/ParserTester.h" + +namespace Swift { + typedef ParserTester<StanzaParser> StanzaParserTester; +} + +#endif diff --git a/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp new file mode 100644 index 0000000..7fd0512 --- /dev/null +++ b/Swiften/Parser/UnitTest/StreamFeaturesParserTest.cpp @@ -0,0 +1,63 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/StreamFeaturesParser.h" +#include "Swiften/Parser/UnitTest/ElementParserTester.h" + +using namespace Swift; + +class StreamFeaturesParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamFeaturesParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_Empty); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamFeaturesParserTest() {} + + void testParse() { + StreamFeaturesParser testling; + ElementParserTester parser(&testling); + + CPPUNIT_ASSERT(parser.parse( + "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>" + "<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>" + "<compression xmlns=\"http://jabber.org/features/compress\">" + "<method>zlib</method>" + "<method>lzw</method>" + "</compression>" + "<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + "<mechanism>DIGEST-MD5</mechanism>" + "<mechanism>PLAIN</mechanism>" + "</mechanisms>" + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>" + "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" + "</stream:features>")); + + StreamFeatures* element = dynamic_cast<StreamFeatures*>(testling.getElement().get()); + CPPUNIT_ASSERT(element->hasStartTLS()); + CPPUNIT_ASSERT(element->hasSession()); + CPPUNIT_ASSERT(element->hasResourceBind()); + CPPUNIT_ASSERT(element->hasCompressionMethod("zlib")); + CPPUNIT_ASSERT(element->hasCompressionMethod("lzw")); + CPPUNIT_ASSERT(element->hasAuthenticationMechanisms()); + CPPUNIT_ASSERT(element->hasAuthenticationMechanism("DIGEST-MD5")); + CPPUNIT_ASSERT(element->hasAuthenticationMechanism("PLAIN")); + } + + void testParse_Empty() { + StreamFeaturesParser testling; + ElementParserTester parser(&testling); + + parser.parse("<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"); + + StreamFeatures* element = dynamic_cast<StreamFeatures*>(testling.getElement().get()); + CPPUNIT_ASSERT(!element->hasStartTLS()); + CPPUNIT_ASSERT(!element->hasSession()); + CPPUNIT_ASSERT(!element->hasResourceBind()); + CPPUNIT_ASSERT(!element->hasAuthenticationMechanisms()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamFeaturesParserTest); diff --git a/Swiften/Parser/UnitTest/XMLParserTest.cpp b/Swiften/Parser/UnitTest/XMLParserTest.cpp new file mode 100644 index 0000000..a26b31b --- /dev/null +++ b/Swiften/Parser/UnitTest/XMLParserTest.cpp @@ -0,0 +1,194 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> + +#ifdef HAVE_CONFIG_H +#include "Swiften/config.h" +#endif +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParserClient.h" +#ifdef HAVE_EXPAT +#include "Swiften/Parser/ExpatParser.h" +#endif +#ifdef HAVE_LIBXML +#include "Swiften/Parser/LibXMLParser.h" +#endif + +using namespace Swift; + +template <typename ParserType> +class XMLParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMLParserTest); + CPPUNIT_TEST(testParse_NestedElements); + CPPUNIT_TEST(testParse_CharacterData); + CPPUNIT_TEST(testParse_NamespacePrefix); + CPPUNIT_TEST(testParse_UnhandledXML); + CPPUNIT_TEST(testParse_InvalidXML); + CPPUNIT_TEST(testParse_InErrorState); + CPPUNIT_TEST(testParse_Incremental); + CPPUNIT_TEST_SUITE_END(); + + public: + XMLParserTest() {} + + void testParse_NestedElements() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse( + "<iq type=\"get\">" + "<query xmlns='jabber:iq:version'/>" + "</iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.size()); + CPPUNIT_ASSERT_EQUAL(String("get"), client_.events[0].attributes["type"]); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[1].data); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[1].attributes.size()); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:version"), client_.events[1].attributes["xmlns"]); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("query"), client_.events[2].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[3].data); + } + + void testParse_CharacterData() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<html>bla<i>bli</i>blo</html>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("html"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("bla"), client_.events[1].data); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("i"), client_.events[2].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("bli"), client_.events[3].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type); + CPPUNIT_ASSERT_EQUAL(String("i"), client_.events[4].data); + + CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[5].type); + CPPUNIT_ASSERT_EQUAL(String("blo"), client_.events[5].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[6].type); + CPPUNIT_ASSERT_EQUAL(String("html"), client_.events[6].data); + } + + void testParse_NamespacePrefix() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<p:x xmlns:p='bla'><p:y/></p:x>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("p:x"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("p:y"), client_.events[1].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(String("p:y"), client_.events[2].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(String("p:x"), client_.events[3].data); + } + + void testParse_UnhandledXML() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<iq><!-- Testing --></iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[1].data); + } + + void testParse_InvalidXML() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>")); + } + + void testParse_InErrorState() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>")); + CPPUNIT_ASSERT(!testling.parse("<iq/>")); + } + + void testParse_Incremental() { + ParserType testling(&client_); + + CPPUNIT_ASSERT(testling.parse("<iq")); + CPPUNIT_ASSERT(testling.parse("></iq>")); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size()); + + CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[0].data); + + CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(String("iq"), client_.events[1].data); + } + + private: + class Client : public XMLParserClient { + public: + enum Type { StartElement, EndElement, CharacterData }; + struct Event { + Event( + Type type, + const String& data, + const AttributeMap& attributes) + : type(type), data(data), attributes(attributes) {} + Event(Type type, const String& data) + : type(type), data(data) {} + + Type type; + String data; + AttributeMap attributes; + }; + + Client() {} + + virtual void handleStartElement(const String& element, const AttributeMap& attributes) { + events.push_back(Event(StartElement, element, attributes)); + } + + virtual void handleEndElement(const String& element) { + events.push_back(Event(EndElement, element)); + } + + virtual void handleCharacterData(const String& data) { + events.push_back(Event(CharacterData, data)); + } + + std::vector<Event> events; + } client_; +}; + +#ifdef HAVE_EXPAT +CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<ExpatParser>); +#endif +#ifdef HAVE_LIBXML +CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<LibXMLParser>); +#endif diff --git a/Swiften/Parser/UnitTest/XMPPParserTest.cpp b/Swiften/Parser/UnitTest/XMPPParserTest.cpp new file mode 100644 index 0000000..787828c --- /dev/null +++ b/Swiften/Parser/UnitTest/XMPPParserTest.cpp @@ -0,0 +1,166 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/XMPPParserClient.h" +#include "Swiften/Parser/PayloadParserFactoryCollection.h" +#include "Swiften/Elements/Presence.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Message.h" +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Elements/UnknownElement.h" + +using namespace Swift; + +class XMPPParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPParserTest); + CPPUNIT_TEST(testParse_SimpleSession); + CPPUNIT_TEST(testParse_Presence); + CPPUNIT_TEST(testParse_IQ); + CPPUNIT_TEST(testParse_Message); + CPPUNIT_TEST(testParse_StreamFeatures); + CPPUNIT_TEST(testParse_UnknownElement); + CPPUNIT_TEST(testParse_StrayCharacterData); + CPPUNIT_TEST(testParse_InvalidStreamStart); + CPPUNIT_TEST_SUITE_END(); + + public: + XMPPParserTest() {} + + void testParse_SimpleSession() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<?xml version='1.0'?>")); + CPPUNIT_ASSERT(testling.parse("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' >")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + CPPUNIT_ASSERT(testling.parse("</stream:stream>")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::StreamStart, client_.events[0].type); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[3].type); + CPPUNIT_ASSERT_EQUAL(Client::StreamEnd, client_.events[4].type); + } + + void testParse_Presence() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<Presence*>(client_.events[1].element.get())); + } + + void testParse_IQ() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<IQ*>(client_.events[1].element.get())); + } + + void testParse_Message() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<message/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<Message*>(client_.events[1].element.get())); + } + + void testParse_StreamFeatures() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<stream:features/>")); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[1].type); + CPPUNIT_ASSERT(dynamic_cast<StreamFeatures*>(client_.events[1].element.get())); + } + + void testParse_UnknownElement() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("<foo/>")); + CPPUNIT_ASSERT(testling.parse("<bar/>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT(dynamic_cast<UnknownElement*>(client_.events[2].element.get())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[3].type); + CPPUNIT_ASSERT(dynamic_cast<UnknownElement*>(client_.events[3].element.get())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[4].type); + CPPUNIT_ASSERT(dynamic_cast<Presence*>(client_.events[4].element.get())); + } + + void testParse_StrayCharacterData() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(testling.parse("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'>")); + CPPUNIT_ASSERT(testling.parse("<presence/>")); + CPPUNIT_ASSERT(testling.parse("bla")); + CPPUNIT_ASSERT(testling.parse("<iq/>")); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(client_.events.size())); + CPPUNIT_ASSERT_EQUAL(Client::ElementEvent, client_.events[2].type); + CPPUNIT_ASSERT(dynamic_cast<IQ*>(client_.events[2].element.get())); + } + + void testParse_InvalidStreamStart() { + XMPPParser testling(&client_, &factories_); + + CPPUNIT_ASSERT(!testling.parse("<tream>")); + } + + private: + class Client : public XMPPParserClient { + public: + enum Type { StreamStart, ElementEvent, StreamEnd }; + struct Event { + Event(Type type, boost::shared_ptr<Element> element) + : type(type), element(element) {} + + Event(Type type) : type(type) {} + + Type type; + boost::shared_ptr<Element> element; + }; + + Client() {} + + void handleStreamStart() { + events.push_back(Event(StreamStart)); + } + + void handleElement(boost::shared_ptr<Element> element) { + events.push_back(Event(ElementEvent, element)); + } + + void handleStreamEnd() { + events.push_back(Event(StreamEnd)); + } + + std::vector<Event> events; + } client_; + PayloadParserFactoryCollection factories_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPParserTest); diff --git a/Swiften/Parser/UnknownElementParser.h b/Swiften/Parser/UnknownElementParser.h new file mode 100644 index 0000000..c016664 --- /dev/null +++ b/Swiften/Parser/UnknownElementParser.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_UnknownElementParser_H +#define SWIFTEN_UnknownElementParser_H + +#include "Swiften/Parser/GenericElementParser.h" +#include "Swiften/Elements/UnknownElement.h" + +namespace Swift { + class UnknownElementParser : public GenericElementParser<UnknownElement> { + public: + UnknownElementParser() : GenericElementParser<UnknownElement>() {} + }; +} + +#endif diff --git a/Swiften/Parser/UnknownPayloadParser.h b/Swiften/Parser/UnknownPayloadParser.h new file mode 100644 index 0000000..ad56885 --- /dev/null +++ b/Swiften/Parser/UnknownPayloadParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_UNKNOWNPAYLOADPARSER_H +#define SWIFTEN_UNKNOWNPAYLOADPARSER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Parser/PayloadParser.h" + +namespace Swift { + class String; + + class UnknownPayloadParser : public PayloadParser { + public: + UnknownPayloadParser() {} + + virtual void handleStartElement(const String&, const String&, const AttributeMap&) {} + virtual void handleEndElement(const String&, const String&) {} + virtual void handleCharacterData(const String&) {} + + virtual boost::shared_ptr<Payload> getPayload() const { + return boost::shared_ptr<Payload>(); + } + }; +} + +#endif diff --git a/Swiften/Parser/XMLParser.cpp b/Swiften/Parser/XMLParser.cpp new file mode 100644 index 0000000..a827e99 --- /dev/null +++ b/Swiften/Parser/XMLParser.cpp @@ -0,0 +1,11 @@ +#include "Swiften/Parser/XMLParser.h" + +namespace Swift { + +XMLParser::XMLParser(XMLParserClient* client) : client_(client) { +} + +XMLParser::~XMLParser() { +} + +} diff --git a/Swiften/Parser/XMLParser.h b/Swiften/Parser/XMLParser.h new file mode 100644 index 0000000..7ed90db --- /dev/null +++ b/Swiften/Parser/XMLParser.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_XMLParser_H +#define SWIFTEN_XMLParser_H + +namespace Swift { + class String; + class XMLParserClient; + + class XMLParser { + public: + XMLParser(XMLParserClient* client); + virtual ~XMLParser(); + + virtual bool parse(const String& data) = 0; + + protected: + XMLParserClient* getClient() const { + return client_; + } + + private: + XMLParserClient* client_; + }; +} + +#endif diff --git a/Swiften/Parser/XMLParserClient.cpp b/Swiften/Parser/XMLParserClient.cpp new file mode 100644 index 0000000..53a103f --- /dev/null +++ b/Swiften/Parser/XMLParserClient.cpp @@ -0,0 +1,9 @@ +#include "Swiften/Parser/XMLParserClient.h" + +namespace Swift { + +XMLParserClient::~XMLParserClient() { +} + +} + diff --git a/Swiften/Parser/XMLParserClient.h b/Swiften/Parser/XMLParserClient.h new file mode 100644 index 0000000..7179ac6 --- /dev/null +++ b/Swiften/Parser/XMLParserClient.h @@ -0,0 +1,19 @@ +#ifndef XMLPARSERCLIENT_H +#define XMLPARSERCLIENT_H + +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class String; + + class XMLParserClient { + public: + virtual ~XMLParserClient(); + + virtual void handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) = 0; + virtual void handleEndElement(const String& element, const String& ns) = 0; + virtual void handleCharacterData(const String& data) = 0; + }; +} + +#endif diff --git a/Swiften/Parser/XMLParserFactory.cpp b/Swiften/Parser/XMLParserFactory.cpp new file mode 100644 index 0000000..e21bb5c --- /dev/null +++ b/Swiften/Parser/XMLParserFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/XMLParserFactory.h" + +namespace Swift { + +XMLParserFactory::~XMLParserFactory() { +} + +} diff --git a/Swiften/Parser/XMLParserFactory.h b/Swiften/Parser/XMLParserFactory.h new file mode 100644 index 0000000..8d67b17 --- /dev/null +++ b/Swiften/Parser/XMLParserFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_XMLParserFactory_H +#define SWIFTEN_XMLParserFactory_H + +namespace Swift { + class XMLParser; + class XMLParserClient; + + class XMLParserFactory { + public: + virtual ~XMLParserFactory(); + + virtual XMLParser* createXMLParser(XMLParserClient*) = 0; + }; +} + +#endif diff --git a/Swiften/Parser/XMPPParser.cpp b/Swiften/Parser/XMPPParser.cpp new file mode 100644 index 0000000..e05cbca --- /dev/null +++ b/Swiften/Parser/XMPPParser.cpp @@ -0,0 +1,145 @@ +#include "Swiften/Parser/XMPPParser.h" + +#include <iostream> +#include <cassert> + +#include "Swiften/Base/String.h" +#include "Swiften/Parser/XMLParser.h" +#include "Swiften/Parser/PlatformXMLParserFactory.h" +#include "Swiften/Parser/XMPPParserClient.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Parser/ElementParser.h" +#include "Swiften/Parser/PresenceParser.h" +#include "Swiften/Parser/IQParser.h" +#include "Swiften/Parser/MessageParser.h" +#include "Swiften/Parser/StreamFeaturesParser.h" +#include "Swiften/Parser/AuthRequestParser.h" +#include "Swiften/Parser/AuthSuccessParser.h" +#include "Swiften/Parser/AuthFailureParser.h" +#include "Swiften/Parser/StartTLSParser.h" +#include "Swiften/Parser/StartTLSFailureParser.h" +#include "Swiften/Parser/CompressParser.h" +#include "Swiften/Parser/CompressFailureParser.h" +#include "Swiften/Parser/CompressedParser.h" +#include "Swiften/Parser/UnknownElementParser.h" +#include "Swiften/Parser/TLSProceedParser.h" + +// TODO: Whenever an error occurs in the handlers, stop the parser by returing +// a bool value, and stopping the XML parser + +namespace Swift { + +XMPPParser::XMPPParser( + XMPPParserClient* client, + PayloadParserFactoryCollection* payloadParserFactories) : + xmlParser_(0), + client_(client), + payloadParserFactories_(payloadParserFactories), + currentDepth_(0), + currentElementParser_(0), + parseErrorOccurred_(false) { + xmlParser_ = PlatformXMLParserFactory().createXMLParser(this); +} + +XMPPParser::~XMPPParser() { + delete currentElementParser_; + delete xmlParser_; +} + +bool XMPPParser::parse(const String& data) { + bool xmlParseResult = xmlParser_->parse(data); + return xmlParseResult && !parseErrorOccurred_; +} + +void XMPPParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (!inStream()) { + if (element == "stream" && ns == "http://etherx.jabber.org/streams") { + client_->handleStreamStart(); + } + else { + parseErrorOccurred_ = true; + } + } + else { + if (!inElement()) { + assert(!currentElementParser_); + delete currentElementParser_; + currentElementParser_ = createElementParser(element, ns); + } + currentElementParser_->handleStartElement(element, ns, attributes); + } + ++currentDepth_; +} + +void XMPPParser::handleEndElement(const String& element, const String& ns) { + assert(inStream()); + if (inElement()) { + assert(currentElementParser_); + currentElementParser_->handleEndElement(element, ns); + --currentDepth_; + if (!inElement()) { + client_->handleElement(currentElementParser_->getElement()); + delete currentElementParser_; + currentElementParser_ = 0; + } + } + else { + assert(element == "stream"); + --currentDepth_; + client_->handleStreamEnd(); + } +} + +void XMPPParser::handleCharacterData(const String& data) { + if (currentElementParser_) { + currentElementParser_->handleCharacterData(data); + } + //else { + // std::cerr << "XMPPParser: Ignoring stray character data: " << data << std::endl; + //} +} + +ElementParser* XMPPParser::createElementParser(const String& element, const String& ns) { + if (element == "presence") { + return new PresenceParser(payloadParserFactories_); + } + else if (element == "iq") { + return new IQParser(payloadParserFactories_); + } + else if (element == "message") { + return new MessageParser(payloadParserFactories_); + } + else if (element == "features" && ns == "http://etherx.jabber.org/streams") { + return new StreamFeaturesParser(); + } + else if (element == "auth") { + return new AuthRequestParser(); + } + else if (element == "success") { + return new AuthSuccessParser(); + } + else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") { + return new AuthFailureParser(); + } + else if (element == "starttls") { + return new StartTLSParser(); + } + else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-tls") { + return new StartTLSFailureParser(); + } + else if (element == "compress") { + return new CompressParser(); + } + else if (element == "compressed") { + return new CompressedParser(); + } + else if (element == "failure" && ns == "http://jabber.org/protocol/compress") { + return new CompressFailureParser(); + } + else if (element == "proceed") { + return new TLSProceedParser(); + } + return new UnknownElementParser(); +} + +} diff --git a/Swiften/Parser/XMPPParser.h b/Swiften/Parser/XMPPParser.h new file mode 100644 index 0000000..9e1109d --- /dev/null +++ b/Swiften/Parser/XMPPParser.h @@ -0,0 +1,54 @@ +#ifndef SWIFTEN_XMPPPARSER_H +#define SWIFTEN_XMPPPARSER_H + +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Parser/XMLParserClient.h" +#include "Swiften/Parser/AttributeMap.h" + +namespace Swift { + class XMLParser; + class XMPPParserClient; + class String; + class ElementParser; + class PayloadParserFactoryCollection; + + class XMPPParser : public XMLParserClient, boost::noncopyable { + public: + XMPPParser( + XMPPParserClient* parserClient, + PayloadParserFactoryCollection* payloadParserFactories); + ~XMPPParser(); + + bool parse(const String&); + + private: + virtual void handleStartElement( + const String& element, + const String& ns, + const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String& ns); + virtual void handleCharacterData(const String& data); + + bool inStream() const { + return currentDepth_ > 0; + } + + bool inElement() const { + return currentDepth_ > 1; + } + + ElementParser* createElementParser(const String& element, const String& xmlns); + + private: + XMLParser* xmlParser_; + XMPPParserClient* client_; + PayloadParserFactoryCollection* payloadParserFactories_; + int currentDepth_; + ElementParser* currentElementParser_; + bool parseErrorOccurred_; + }; +} + +#endif diff --git a/Swiften/Parser/XMPPParserClient.cpp b/Swiften/Parser/XMPPParserClient.cpp new file mode 100644 index 0000000..66ea917 --- /dev/null +++ b/Swiften/Parser/XMPPParserClient.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Parser/XMPPParserClient.h" + +namespace Swift { + +XMPPParserClient::~XMPPParserClient() { +} + +} diff --git a/Swiften/Parser/XMPPParserClient.h b/Swiften/Parser/XMPPParserClient.h new file mode 100644 index 0000000..abecc71 --- /dev/null +++ b/Swiften/Parser/XMPPParserClient.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_XMPPPARSERCLIENT_H +#define SWIFTEN_XMPPPARSERCLIENT_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Element.h" + +namespace Swift { + class XMPPParserClient + { + public: + virtual ~XMPPParserClient(); + + virtual void handleStreamStart() = 0; + virtual void handleElement(boost::shared_ptr<Element>) = 0; + virtual void handleStreamEnd() = 0; + }; +} + +#endif diff --git a/Swiften/Presence/Makefile.inc b/Swiften/Presence/Makefile.inc new file mode 100644 index 0000000..ca8c95e --- /dev/null +++ b/Swiften/Presence/Makefile.inc @@ -0,0 +1,4 @@ +SWIFTEN_SOURCES += \ + Swiften/Presence/PresenceOracle.cpp + +include Swiften/Presence/UnitTest/Makefile.inc diff --git a/Swiften/Presence/PresenceOracle.cpp b/Swiften/Presence/PresenceOracle.cpp new file mode 100644 index 0000000..af98510 --- /dev/null +++ b/Swiften/Presence/PresenceOracle.cpp @@ -0,0 +1,29 @@ +#include "PresenceOracle.h" + +#include <boost/bind.hpp> +#include "Swiften/Client/StanzaChannel.h" +namespace Swift { + +typedef std::pair<JID, std::map<JID, boost::shared_ptr<Presence> > > JIDMapPair; +typedef std::pair<JID, boost::shared_ptr<Presence> > JIDPresencePair; + +PresenceOracle::PresenceOracle(StanzaChannel* stanzaChannel) { + stanzaChannel->onPresenceReceived.connect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); +} + +void PresenceOracle::handleIncomingPresence(boost::shared_ptr<Presence> presence) { + JID bareJID = JID(presence->getFrom().toBare()); + std::map<JID, boost::shared_ptr<Presence> > jidMap = entries_[bareJID]; + boost::shared_ptr<Presence> last; + foreach(JIDPresencePair pair, jidMap) { + if (pair.first == presence->getFrom()) { + last = pair.second; + break; + } + } + jidMap[presence->getFrom()] = presence; + entries_[bareJID] = jidMap; + onPresenceChange(presence, last); +} + +} diff --git a/Swiften/Presence/PresenceOracle.h b/Swiften/Presence/PresenceOracle.h new file mode 100644 index 0000000..81f289b --- /dev/null +++ b/Swiften/Presence/PresenceOracle.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Presence.h" + +#include <map> +#include <boost/signal.hpp> + +namespace Swift { +class StanzaChannel; + +class PresenceOracle { + public: + PresenceOracle(StanzaChannel* stanzaChannel); + ~PresenceOracle() {}; + + boost::signal<void (boost::shared_ptr<Presence>, boost::shared_ptr<Presence>)> onPresenceChange; + + private: + void handleIncomingPresence(boost::shared_ptr<Presence> presence); + std::map<JID, std::map<JID, boost::shared_ptr<Presence> > > entries_; + StanzaChannel* stanzaChannel_; +}; +} + diff --git a/Swiften/Presence/UnitTest/Makefile.inc b/Swiften/Presence/UnitTest/Makefile.inc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Swiften/Presence/UnitTest/Makefile.inc diff --git a/Swiften/QA/ClientTest/ClientTest.cpp b/Swiften/QA/ClientTest/ClientTest.cpp new file mode 100644 index 0000000..20a03a4 --- /dev/null +++ b/Swiften/QA/ClientTest/ClientTest.cpp @@ -0,0 +1,63 @@ +#include <boost/bind.hpp> +#include <boost/thread.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/Network/Timer.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Queries/Requests/GetRosterRequest.h" + +using namespace Swift; + +SimpleEventLoop eventLoop; + +Client* client = 0; +bool rosterReceived = false; + +void printIncomingData(const String& data) { + std::cout << "<- " << data << std::endl; +} + +void printOutgoingData(const String& data) { + std::cout << "-> " << data << std::endl; +} + +void handleRosterReceived(boost::shared_ptr<Payload>) { + rosterReceived = true; + eventLoop.stop(); +} + +void handleConnected() { + GetRosterRequest* rosterRequest = new GetRosterRequest(client, Request::AutoDeleteAfterResponse); + rosterRequest->onResponse.connect(boost::bind(&handleRosterReceived, _1)); + rosterRequest->send(); +} + +int main(int, char**) { + char* jid = getenv("SWIFT_CLIENTTEST_JID"); + if (!jid) { + std::cerr << "Please set the SWIFT_CLIENTTEST_JID environment variable" << std::endl; + return -1; + } + char* pass = getenv("SWIFT_CLIENTTEST_PASS"); + if (!pass) { + std::cerr << "Please set the SWIFT_CLIENTTEST_PASS environment variable" << std::endl; + return -1; + } + + client = new Swift::Client(JID(jid), String(pass)); + client->onConnected.connect(&handleConnected); + client->onDataRead.connect(&printIncomingData); + client->onDataWritten.connect(&printOutgoingData); + client->connect(); + + { + Timer timer(10000); + timer.onTick.connect(boost::bind(&SimpleEventLoop::stop, &eventLoop)); + timer.start(); + + eventLoop.run(); + } + delete client; + return !rosterReceived; +} diff --git a/Swiften/QA/ClientTest/Makefile.inc b/Swiften/QA/ClientTest/Makefile.inc new file mode 100644 index 0000000..d01ccf8 --- /dev/null +++ b/Swiften/QA/ClientTest/Makefile.inc @@ -0,0 +1,16 @@ +CLIENTTEST_TARGET = Swiften/QA/ClientTest/ClientTest +CLIENTTEST_SOURCES += \ + Swiften/QA/ClientTest/ClientTest.cpp +CLIENTTEST_OBJECTS = \ + $(CLIENTTEST_SOURCES:.cpp=.o) + +TEST_TARGETS += ClientTest + +CLEANFILES += $(CLIENTTEST_OBJECTS) $(CLIENTTEST_TARGET) + +$(CLIENTTEST_TARGET): $(SWIFTEN_TARGET) $(CLIENTTEST_OBJECTS) + $(QUIET_LINK)$(CXX) -o $(CLIENTTEST_TARGET) $(CLIENTTEST_OBJECTS) $(LDFLAGS) $(CPPCLIENT_LDFLAGS) $(SWIFTEN_TARGET) $(LIBS) + +.PHONY: ClientTest +ClientTest: $(CLIENTTEST_TARGET) + $(TEST_RUNNER) $(CLIENTTEST_TARGET) diff --git a/Swiften/QA/Makefile.inc b/Swiften/QA/Makefile.inc new file mode 100644 index 0000000..dc3a0bf --- /dev/null +++ b/Swiften/QA/Makefile.inc @@ -0,0 +1,11 @@ +ifdef USE_VALGRIND +# Not enabled: --show-reachable=yes +TEST_RUNNER=valgrind --suppressions=Swiften/QA/valgrind.supp -q --leak-check=full --track-origins=yes +endif + +include Swiften/QA/UnitTest/Makefile.inc +include Swiften/QA/NetworkTest/Makefile.inc +include Swiften/QA/ClientTest/Makefile.inc + +.PHONY: test +test: $(TEST_TARGETS) diff --git a/Swiften/QA/NetworkTest/BoostConnectionTest.cpp b/Swiften/QA/NetworkTest/BoostConnectionTest.cpp new file mode 100644 index 0000000..639097a --- /dev/null +++ b/Swiften/QA/NetworkTest/BoostConnectionTest.cpp @@ -0,0 +1,51 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/sleep.h" +#include "Swiften/Network/BoostConnection.h" +#include "Swiften/EventLoop/DummyEventLoop.h" + +using namespace Swift; + +class BoostConnectionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(BoostConnectionTest); + CPPUNIT_TEST(testDestructor); + CPPUNIT_TEST(testDestructor_PendingEvents); + CPPUNIT_TEST_SUITE_END(); + + public: + BoostConnectionTest() {} + + void setUp() { + eventLoop_ = new DummyEventLoop(); + } + + void tearDown() { + delete eventLoop_; + } + + void testDestructor() { + { + std::string domain("el-tramo.be"); + std::auto_ptr<BoostConnection> testling(new BoostConnection(domain)); + testling->connect(); + } + } + + void testDestructor_PendingEvents() { + { + std::auto_ptr<BoostConnection> testling(new BoostConnection("el-tramo.be")); + testling->connect(); + while (!eventLoop_->hasEvents()) { + Swift::sleep(10); + } + } + eventLoop_->processEvents(); + } + + private: + DummyEventLoop* eventLoop_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BoostConnectionTest); diff --git a/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp b/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp new file mode 100644 index 0000000..8968efd --- /dev/null +++ b/Swiften/QA/NetworkTest/DomainNameResolverTest.cpp @@ -0,0 +1,64 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/String.h" +#include "Swiften/Network/DomainNameResolver.h" +#include "Swiften/Network/DomainNameResolveException.h" + +using namespace Swift; + +class DomainNameResolverTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DomainNameResolverTest); + CPPUNIT_TEST(testResolve_NoSRV); + CPPUNIT_TEST(testResolve_SRV); + CPPUNIT_TEST(testResolve_Invalid); + //CPPUNIT_TEST(testResolve_IPv6); + CPPUNIT_TEST(testResolve_International); + CPPUNIT_TEST_SUITE_END(); + + public: + DomainNameResolverTest() {} + + void setUp() { + resolver_ = new DomainNameResolver(); + } + + void tearDown() { + delete resolver_; + } + + void testResolve_NoSRV() { + HostAddressPort result = resolver_->resolve("xmpp.test.swift.im"); + + CPPUNIT_ASSERT_EQUAL(std::string("10.0.0.0"), result.getAddress().toString()); + CPPUNIT_ASSERT_EQUAL(5222, result.getPort()); + } + + void testResolve_SRV() { + HostAddressPort result = resolver_->resolve("xmpp-srv.test.swift.im"); + + CPPUNIT_ASSERT_EQUAL(std::string("10.0.0.1"), result.getAddress().toString()); + CPPUNIT_ASSERT_EQUAL(5000, result.getPort()); + } + + void testResolve_Invalid() { + CPPUNIT_ASSERT_THROW(resolver_->resolve("invalid.test.swift.im"), DomainNameResolveException); + } + + void testResolve_IPv6() { + HostAddressPort result = resolver_->resolve("xmpp-ipv6.test.swift.im"); + CPPUNIT_ASSERT_EQUAL(std::string("0000:0000:0000:0000:0000:ffff:0a00:0104"), result.getAddress().toString()); + CPPUNIT_ASSERT_EQUAL(5222, result.getPort()); + } + + void testResolve_International() { + HostAddressPort result = resolver_->resolve("tron\xc3\xa7on.test.swift.im"); + CPPUNIT_ASSERT_EQUAL(std::string("10.0.0.3"), result.getAddress().toString()); + CPPUNIT_ASSERT_EQUAL(5222, result.getPort()); + } + + private: + DomainNameResolver* resolver_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DomainNameResolverTest); diff --git a/Swiften/QA/NetworkTest/Makefile.inc b/Swiften/QA/NetworkTest/Makefile.inc new file mode 100644 index 0000000..6924a70 --- /dev/null +++ b/Swiften/QA/NetworkTest/Makefile.inc @@ -0,0 +1,18 @@ +NETWORKTEST_TARGET += Swiften/QA/NetworkTest/checker +NETWORKTEST_SOURCES += \ + Swiften/QA/NetworkTest/DomainNameResolverTest.cpp \ + Swiften/QA/NetworkTest/BoostConnectionTest.cpp \ + Swiften/QA/UnitTest/checker.cpp +NETWORKTEST_OBJECTS = \ + $(NETWORKTEST_SOURCES:.cpp=.o) + +TEST_TARGETS += NetworkTest + +CLEANFILES += $(NETWORKTEST_OBJECTS) $(NETWORKTEST_TARGET) + +$(NETWORKTEST_TARGET): $(SWIFTEN_TARGET) $(CPPUNIT_TARGET) $(NETWORKTEST_OBJECTS) + $(QUIET_LINK)$(CXX) -o $(NETWORKTEST_TARGET) $(NETWORKTEST_OBJECTS) $(LDFLAGS) $(CPPNETWORK_LDFLAGS) $(SWIFTEN_TARGET) $(CPPUNIT_TARGET) $(LIBS) + +.PHONY: NetworkTest +NetworkTest: $(NETWORKTEST_TARGET) + $(TEST_RUNNER) $(NETWORKTEST_TARGET) diff --git a/Swiften/QA/UnitTest/Makefile.inc b/Swiften/QA/UnitTest/Makefile.inc new file mode 100644 index 0000000..5b456db --- /dev/null +++ b/Swiften/QA/UnitTest/Makefile.inc @@ -0,0 +1,17 @@ +UNITTEST_TARGET = Swiften/QA/UnitTest/checker +UNITTEST_SOURCES += \ + Swiften/QA/UnitTest/checker.cpp +UNITTEST_OBJECTS = \ + $(UNITTEST_SOURCES:.cpp=.o) + +TEST_TARGETS += check + +CLEANFILES += $(UNITTEST_OBJECTS) $(UNITTEST_TARGET) + +.PHONY: check +check: $(UNITTEST_TARGET) + $(TEST_RUNNER) ./$(UNITTEST_TARGET) + +$(UNITTEST_TARGET): $(SWIFTEN_TARGET) $(CPPUNIT_TARGET) $(UNITTEST_OBJECTS) + $(QUIET_LINK)$(CXX) -o $(UNITTEST_TARGET) $(UNITTEST_OBJECTS) $(LDFLAGS) $(SWIFTEN_TARGET) $(CPPUNIT_TARGET) $(LIBS) + diff --git a/Swiften/QA/UnitTest/checker.cpp b/Swiften/QA/UnitTest/checker.cpp new file mode 100644 index 0000000..ea4f0d9 --- /dev/null +++ b/Swiften/QA/UnitTest/checker.cpp @@ -0,0 +1,16 @@ +#include <string> +#include <cppunit/ui/text/TestRunner.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/XmlOutputter.h> +#include <cppunit/TextTestResult.h> + +int main(int argc, char* argv[]) +{ + CppUnit::TestFactoryRegistry& registry = CppUnit::TestFactoryRegistry::getRegistry(); + CppUnit::TextUi::TestRunner runner; + runner.addTest( registry.makeTest() ); + if (argc >= 2 && std::string(argv[1]) != std::string("--xml")) { + runner.setOutputter(new CppUnit::XmlOutputter(&runner.result(), std::cout)); + } + return (runner.run("") ? 0 : 1); +} diff --git a/Swiften/QA/UnitTest/template/FooTest.cpp b/Swiften/QA/UnitTest/template/FooTest.cpp new file mode 100644 index 0000000..b6b9abf --- /dev/null +++ b/Swiften/QA/UnitTest/template/FooTest.cpp @@ -0,0 +1,24 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +using namespace Swift; + +class FooTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(FooTest); + CPPUNIT_TEST(testBar); + CPPUNIT_TEST_SUITE_END(); + + public: + FooTest() {} + + void setUp() { + } + + void tearDown() { + } + + void testBar() { + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(FooTest); diff --git a/Swiften/QA/valgrind.supp b/Swiften/QA/valgrind.supp new file mode 100644 index 0000000..5e2ee00 --- /dev/null +++ b/Swiften/QA/valgrind.supp @@ -0,0 +1,51 @@ +{ + ZLib doesn't allocate its buffer. This is no bug according to the FAQ. + Memcheck:Cond + fun:longest_match + fun:deflate_slow + fun:deflate +} + +{ + Not sure why this happens. + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.1 + fun:_ZN5boost6thread12start_threadEv +} + +{ + <insert a suppression name here> + Memcheck:Param + socketcall.sendto(msg) + fun:sendto + fun:getaddrinfo +} + +{ + <insert a suppression name here> + Memcheck:Cond + fun:BN_bin2bn +} + +{ + <insert a suppression name here> + Memcheck:Cond + fun:BN_num_bits_word +} + +{ + <insert a suppression name here> + Memcheck:Value4 + fun:BN_mod_exp_mont_consttime + fun:BN_mod_exp_mont +} + +{ + <insert a suppression name here> + Memcheck:Value4 + fun:BN_num_bits_word + fun:BN_mod_exp_mont_consttime + fun:BN_mod_exp_mont +} diff --git a/Swiften/Queries/DummyIQChannel.h b/Swiften/Queries/DummyIQChannel.h new file mode 100644 index 0000000..f72d7a5 --- /dev/null +++ b/Swiften/Queries/DummyIQChannel.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_DummyIQChannel_H +#define SWIFTEN_DummyIQChannel_H + +#include <vector> + +#include "Swiften/Queries/IQChannel.h" + +namespace Swift { + class DummyIQChannel : public IQChannel { + public: + DummyIQChannel() {} + + virtual void sendIQ(boost::shared_ptr<IQ> iq) { + iqs_.push_back(iq); + } + + virtual String getNewIQID() { + return "test-id"; + } + + std::vector<boost::shared_ptr<IQ> > iqs_; + }; +} + +#endif diff --git a/Swiften/Queries/GenericRequest.h b/Swiften/Queries/GenericRequest.h new file mode 100644 index 0000000..c81760f --- /dev/null +++ b/Swiften/Queries/GenericRequest.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_GenericRequest_H +#define SWIFTEN_GenericRequest_H + +#include <boost/signal.hpp> + +#include "Swiften/Queries/Request.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class GenericRequest : public Request { + public: + GenericRequest( + IQ::Type type, + const JID& receiver, + boost::shared_ptr<Payload> payload, + IQRouter* router, + AutoDeleteBehavior autoDeleteBehavior = DoNotAutoDelete) : + Request(type, receiver, payload, router, autoDeleteBehavior) { + } + + virtual void handleResponse(boost::shared_ptr<Payload> payload, boost::optional<Error> error) { + onResponse(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(payload), error); + } + + public: + boost::signal<void (boost::shared_ptr<PAYLOAD_TYPE>, const boost::optional<Error>&)> onResponse; + }; +} + +#endif diff --git a/Swiften/Queries/IQChannel.cpp b/Swiften/Queries/IQChannel.cpp new file mode 100644 index 0000000..539dea0 --- /dev/null +++ b/Swiften/Queries/IQChannel.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Queries/IQChannel.h" + +namespace Swift { + +IQChannel::~IQChannel() { +} + +} diff --git a/Swiften/Queries/IQChannel.h b/Swiften/Queries/IQChannel.h new file mode 100644 index 0000000..0dbb1be --- /dev/null +++ b/Swiften/Queries/IQChannel.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_IQChannel_H +#define SWIFTEN_IQChannel_H + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQChannel { + public: + virtual ~IQChannel(); + + virtual void sendIQ(boost::shared_ptr<IQ>) = 0; + virtual String getNewIQID() = 0; + + boost::signal<void (boost::shared_ptr<IQ>)> onIQReceived; + }; +} + +#endif diff --git a/Swiften/Queries/IQHandler.cpp b/Swiften/Queries/IQHandler.cpp new file mode 100644 index 0000000..b5e7e49 --- /dev/null +++ b/Swiften/Queries/IQHandler.cpp @@ -0,0 +1,14 @@ +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQRouter.h" + +namespace Swift { + +IQHandler::IQHandler(IQRouter* router) : router_(router) { + router_->addHandler(this); +} + +IQHandler::~IQHandler() { + router_->removeHandler(this); +} + +} diff --git a/Swiften/Queries/IQHandler.h b/Swiften/Queries/IQHandler.h new file mode 100644 index 0000000..7a8d008 --- /dev/null +++ b/Swiften/Queries/IQHandler.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_IQHandler_H +#define SWIFTEN_IQHandler_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQRouter; + + class IQHandler { + public: + IQHandler(IQRouter* router); + virtual ~IQHandler(); + + virtual bool handleIQ(boost::shared_ptr<IQ>) = 0; + + protected: + IQRouter* getRouter() const { + return router_; + } + + private: + IQRouter* router_; + }; +} + +#endif diff --git a/Swiften/Queries/IQRouter.cpp b/Swiften/Queries/IQRouter.cpp new file mode 100644 index 0000000..b474640 --- /dev/null +++ b/Swiften/Queries/IQRouter.cpp @@ -0,0 +1,46 @@ +#include "Swiften/Queries/IQRouter.h" + +#include <algorithm> +#include <boost/bind.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Queries/IQChannel.h" +#include "Swiften/Elements/Error.h" + +namespace Swift { + +IQRouter::IQRouter(IQChannel* channel) : channel_(channel) { + channel->onIQReceived.connect(boost::bind(&IQRouter::handleIQ, this, _1)); +} + +void IQRouter::handleIQ(boost::shared_ptr<IQ> iq) { + bool handled = false; + foreach(IQHandler* handler, handlers_) { + handled |= handler->handleIQ(iq); + if (handled) { + break; + } + } + if (!handled && (iq->getType() == IQ::Get || iq->getType() == IQ::Set) ) { + channel_->sendIQ(IQ::createError(iq->getFrom(), iq->getID(), Error::FeatureNotImplemented, Error::Cancel)); + } +} + +void IQRouter::addHandler(IQHandler* handler) { + handlers_.push_back(handler); +} + +void IQRouter::removeHandler(IQHandler* handler) { + handlers_.erase(std::remove(handlers_.begin(), handlers_.end(), handler), handlers_.end()); +} + +void IQRouter::sendIQ(boost::shared_ptr<IQ> iq) { + channel_->sendIQ(iq); +} + +String IQRouter::getNewIQID() { + return channel_->getNewIQID(); +} + +} diff --git a/Swiften/Queries/IQRouter.h b/Swiften/Queries/IQRouter.h new file mode 100644 index 0000000..2240dfb --- /dev/null +++ b/Swiften/Queries/IQRouter.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_IQRouter_H +#define SWIFTEN_IQRouter_H + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/IQ.h" + +namespace Swift { + class IQChannel; + class IQHandler; + + class IQRouter { + public: + IQRouter(IQChannel* channel); + + void addHandler(IQHandler* handler); + void removeHandler(IQHandler* handler); + + void sendIQ(boost::shared_ptr<IQ> iq); + String getNewIQID(); + + private: + void handleIQ(boost::shared_ptr<IQ> iq); + + private: + IQChannel* channel_; + std::vector<IQHandler*> handlers_; + }; +} + +#endif diff --git a/Swiften/Queries/Makefile.inc b/Swiften/Queries/Makefile.inc new file mode 100644 index 0000000..53a712d --- /dev/null +++ b/Swiften/Queries/Makefile.inc @@ -0,0 +1,8 @@ +SWIFTEN_SOURCES += \ + Swiften/Queries/IQRouter.cpp \ + Swiften/Queries/IQHandler.cpp \ + Swiften/Queries/IQChannel.cpp \ + Swiften/Queries/Request.cpp + +include Swiften/Queries/Responders/Makefile.inc +include Swiften/Queries/UnitTest/Makefile.inc diff --git a/Swiften/Queries/Request.cpp b/Swiften/Queries/Request.cpp new file mode 100644 index 0000000..ac361cc --- /dev/null +++ b/Swiften/Queries/Request.cpp @@ -0,0 +1,39 @@ +#include "Swiften/Queries/Request.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/EventLoop/MainEventLoop.h" + +namespace Swift { + +Request::Request(IQ::Type type, const JID& receiver, boost::shared_ptr<Payload> payload, IQRouter* router, AutoDeleteBehavior autoDeleteBehavior) : IQHandler(router), type_(type), receiver_(receiver), payload_(payload), autoDeleteBehavior_(autoDeleteBehavior) { + id_ = getRouter()->getNewIQID(); +} + +void Request::send() { + boost::shared_ptr<IQ> iq(new IQ(type_)); + iq->setTo(receiver_); + iq->addPayload(payload_); + iq->setID(id_); + getRouter()->sendIQ(iq); +} + +bool Request::handleIQ(boost::shared_ptr<IQ> iq) { + if (iq->getID() == id_) { + if (iq->getType() == IQ::Result) { + handleResponse(iq->getPayloadOfSameType(payload_), boost::optional<Error>()); + if (autoDeleteBehavior_ == AutoDeleteAfterResponse) { + MainEventLoop::deleteLater(this); + } + return true; + } + else { + // FIXME: Get proper error + handleResponse(boost::shared_ptr<Payload>(), boost::optional<Error>(Error::UndefinedCondition)); + return true; + } + } + else { + return false; + } +} + +} diff --git a/Swiften/Queries/Request.h b/Swiften/Queries/Request.h new file mode 100644 index 0000000..f084303 --- /dev/null +++ b/Swiften/Queries/Request.h @@ -0,0 +1,46 @@ +#ifndef SWIFTEN_Request_H +#define SWIFTEN_Request_H + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/Error.h" +#include "Swiften/JID/JID.h" + +namespace Swift { + class Request : public IQHandler { + public: + enum AutoDeleteBehavior { + DoNotAutoDelete, + AutoDeleteAfterResponse + }; + + Request( + IQ::Type type, + const JID& receiver, + boost::shared_ptr<Payload> payload, + IQRouter* router, + AutoDeleteBehavior = DoNotAutoDelete); + + void send(); + + protected: + virtual void handleResponse(boost::shared_ptr<Payload>, boost::optional<Error>) = 0; + + private: + bool handleIQ(boost::shared_ptr<IQ>); + + private: + IQ::Type type_; + JID receiver_; + boost::shared_ptr<Payload> payload_; + AutoDeleteBehavior autoDeleteBehavior_; + String id_; + }; +} + +#endif diff --git a/Swiften/Queries/Requests/GetDiscoInfoRequest.h b/Swiften/Queries/Requests/GetDiscoInfoRequest.h new file mode 100644 index 0000000..0e8508e --- /dev/null +++ b/Swiften/Queries/Requests/GetDiscoInfoRequest.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_GetDiscoInfoRequest_H +#define SWIFTEN_GetDiscoInfoRequest_H + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class GetDiscoInfoRequest : public GenericRequest<DiscoInfo> { + public: + GetDiscoInfoRequest(const JID& jid, IQRouter* router, AutoDeleteBehavior autoDeleteBehavior = DoNotAutoDelete) : + GenericRequest<DiscoInfo>(IQ::Get, jid, boost::shared_ptr<DiscoInfo>(new DiscoInfo()), router, autoDeleteBehavior) { + } + }; +} + +#endif diff --git a/Swiften/Queries/Requests/GetRosterRequest.h b/Swiften/Queries/Requests/GetRosterRequest.h new file mode 100644 index 0000000..2364d81 --- /dev/null +++ b/Swiften/Queries/Requests/GetRosterRequest.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_GetRosterRequest_H +#define SWIFTEN_GetRosterRequest_H + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/RosterPayload.h" + +namespace Swift { + class GetRosterRequest : public GenericRequest<RosterPayload> { + public: + GetRosterRequest(IQRouter* router, AutoDeleteBehavior autoDeleteBehavior = DoNotAutoDelete) : + GenericRequest<RosterPayload>(IQ::Get, JID(), boost::shared_ptr<Payload>(new RosterPayload()), router, autoDeleteBehavior) { + } + }; +} + +#endif diff --git a/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h new file mode 100644 index 0000000..53ca3eb --- /dev/null +++ b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_GetSecurityLabelsCatalogRequest_H +#define SWIFTEN_GetSecurityLabelsCatalogRequest_H + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/SecurityLabelsCatalog.h" + +namespace Swift { + class GetSecurityLabelsCatalogRequest : public GenericRequest<SecurityLabelsCatalog> { + public: + GetSecurityLabelsCatalogRequest( + const JID& recipient, + IQRouter* router, + AutoDeleteBehavior autoDeleteBehavior = DoNotAutoDelete) : + GenericRequest<SecurityLabelsCatalog>( + IQ::Get, JID(), boost::shared_ptr<SecurityLabelsCatalog>(new SecurityLabelsCatalog(recipient)), router, autoDeleteBehavior) { + } + }; +} + +#endif diff --git a/Swiften/Queries/Responder.h b/Swiften/Queries/Responder.h new file mode 100644 index 0000000..b6029ae --- /dev/null +++ b/Swiften/Queries/Responder.h @@ -0,0 +1,49 @@ +#ifndef SWIFTEN_Responder_H +#define SWIFTEN_Responder_H + +#include "Swiften/Queries/IQHandler.h" +#include "Swiften/Elements/Error.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class Responder : public IQHandler { + public: + Responder(IQRouter* router) : IQHandler(router) { + } + + protected: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<PAYLOAD_TYPE> payload) = 0; + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<PAYLOAD_TYPE> payload) = 0; + + void sendResponse(const JID& to, const String& id, boost::shared_ptr<Payload> payload) { + getRouter()->sendIQ(IQ::createResult(to, id, payload)); + } + + void sendError(const JID& to, const String& id, Error::Condition condition, Error::Type type) { + getRouter()->sendIQ(IQ::createError(to, id, condition, type)); + } + + private: + virtual bool handleIQ(boost::shared_ptr<IQ> iq) { + if (iq->getType() == IQ::Set || iq->getType() == IQ::Get) { + boost::shared_ptr<PAYLOAD_TYPE> payload(iq->getPayload<PAYLOAD_TYPE>()); + if (payload) { + bool result; + if (iq->getType() == IQ::Set) { + result = handleSetRequest(iq->getFrom(), iq->getID(), payload); + } + else { + result = handleGetRequest(iq->getFrom(), iq->getID(), payload); + } + if (!result) { + getRouter()->sendIQ(IQ::createError(iq->getFrom(), iq->getID(), Error::NotAllowed, Error::Cancel)); + } + return true; + } + } + return false; + } + }; +} + +#endif diff --git a/Swiften/Queries/Responders/DiscoInfoResponder.cpp b/Swiften/Queries/Responders/DiscoInfoResponder.cpp new file mode 100644 index 0000000..e207133 --- /dev/null +++ b/Swiften/Queries/Responders/DiscoInfoResponder.cpp @@ -0,0 +1,40 @@ +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + +DiscoInfoResponder::DiscoInfoResponder(IQRouter* router) : Responder<DiscoInfo>(router) { +} + +void DiscoInfoResponder::setDiscoInfo(const DiscoInfo& info) { + info_ = info; +} + +void DiscoInfoResponder::setDiscoInfo(const String& node, const DiscoInfo& info) { + DiscoInfo newInfo(info); + newInfo.setNode(node); + nodeInfo_[node] = newInfo; +} + +bool DiscoInfoResponder::handleGetRequest(const JID& from, const String& id, boost::shared_ptr<DiscoInfo> info) { + if (info->getNode().isEmpty()) { + sendResponse(from, id, boost::shared_ptr<DiscoInfo>(new DiscoInfo(info_))); + } + else { + std::map<String,DiscoInfo>::const_iterator i = nodeInfo_.find(info->getNode()); + if (i != nodeInfo_.end()) { + sendResponse(from, id, boost::shared_ptr<DiscoInfo>(new DiscoInfo((*i).second))); + } + else { + sendError(from, id, Error::ItemNotFound, Error::Cancel); + } + } + return true; +} + +bool DiscoInfoResponder::handleSetRequest(const JID&, const String&, boost::shared_ptr<DiscoInfo>) { + return false; +} + +} diff --git a/Swiften/Queries/Responders/DiscoInfoResponder.h b/Swiften/Queries/Responders/DiscoInfoResponder.h new file mode 100644 index 0000000..aa79163 --- /dev/null +++ b/Swiften/Queries/Responders/DiscoInfoResponder.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_DiscoInfoResponder_H +#define SWIFTEN_DiscoInfoResponder_H + +#include <map> + +#include "Swiften/Queries/Responder.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class IQRouter; + + class DiscoInfoResponder : public Responder<DiscoInfo> { + public: + DiscoInfoResponder(IQRouter* router); + + void setDiscoInfo(const DiscoInfo& info); + void setDiscoInfo(const String& node, const DiscoInfo& info); + + private: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<DiscoInfo> payload); + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<DiscoInfo> payload); + + private: + DiscoInfo info_; + std::map<String, DiscoInfo> nodeInfo_; + }; +} + +#endif diff --git a/Swiften/Queries/Responders/Makefile.inc b/Swiften/Queries/Responders/Makefile.inc new file mode 100644 index 0000000..5049440 --- /dev/null +++ b/Swiften/Queries/Responders/Makefile.inc @@ -0,0 +1,5 @@ +SWIFTEN_SOURCES += \ + Swiften/Queries/Responders/SoftwareVersionResponder.cpp \ + Swiften/Queries/Responders/DiscoInfoResponder.cpp + +include Swiften/Queries/Responders/UnitTest/Makefile.inc diff --git a/Swiften/Queries/Responders/SoftwareVersionResponder.cpp b/Swiften/Queries/Responders/SoftwareVersionResponder.cpp new file mode 100644 index 0000000..dad2442 --- /dev/null +++ b/Swiften/Queries/Responders/SoftwareVersionResponder.cpp @@ -0,0 +1,20 @@ +#include "Swiften/Queries/Responders/SoftwareVersionResponder.h" +#include "Swiften/Queries/IQRouter.h" + +namespace Swift { + +SoftwareVersionResponder::SoftwareVersionResponder( + const String& client, const String& version, IQRouter* router) : + Responder<SoftwareVersion>(router), client_(client), version_(version) { +} + +bool SoftwareVersionResponder::handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion>) { + sendResponse(from, id, boost::shared_ptr<SoftwareVersion>(new SoftwareVersion(client_, version_))); + return true; +} + +bool SoftwareVersionResponder::handleSetRequest(const JID&, const String&, boost::shared_ptr<SoftwareVersion>) { + return false; +} + +} diff --git a/Swiften/Queries/Responders/SoftwareVersionResponder.h b/Swiften/Queries/Responders/SoftwareVersionResponder.h new file mode 100644 index 0000000..d66e168 --- /dev/null +++ b/Swiften/Queries/Responders/SoftwareVersionResponder.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_SoftwareVersionResponder_H +#define SWIFTEN_SoftwareVersionResponder_H + +#include "Swiften/Queries/Responder.h" +#include "Swiften/Elements/SoftwareVersion.h" + +namespace Swift { + class IQRouter; + + class SoftwareVersionResponder : public Responder<SoftwareVersion> { + public: + SoftwareVersionResponder(const String& client, const String& version, IQRouter* router); + + private: + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload); + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload); + + private: + String client_; + String version_; + }; +} + +#endif diff --git a/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp b/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp new file mode 100644 index 0000000..44db138 --- /dev/null +++ b/Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp @@ -0,0 +1,84 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <typeinfo> + +#include "Swiften/Queries/Responders/DiscoInfoResponder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" + +using namespace Swift; + +class DiscoInfoResponderTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(DiscoInfoResponderTest); + CPPUNIT_TEST(testHandleRequest_GetToplevelInfo); + CPPUNIT_TEST(testHandleRequest_GetNodeInfo); + CPPUNIT_TEST(testHandleRequest_GetInvalidNodeInfo); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoResponderTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testHandleRequest_GetToplevelInfo() { + DiscoInfoResponder testling(router_); + DiscoInfo discoInfo; + discoInfo.addFeature("foo"); + testling.setDiscoInfo(discoInfo); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<DiscoInfo> payload(channel_->iqs_[0]->getPayload<DiscoInfo>()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String(""), payload->getNode()); + CPPUNIT_ASSERT(payload->hasFeature("foo")); + } + + void testHandleRequest_GetNodeInfo() { + DiscoInfoResponder testling(router_); + DiscoInfo discoInfo; + discoInfo.addFeature("foo"); + testling.setDiscoInfo(discoInfo); + DiscoInfo discoInfoBar; + discoInfoBar.addFeature("bar"); + testling.setDiscoInfo("bar-node", discoInfoBar); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + query->setNode("bar-node"); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<DiscoInfo> payload(channel_->iqs_[0]->getPayload<DiscoInfo>()); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("bar-node"), payload->getNode()); + CPPUNIT_ASSERT(payload->hasFeature("bar")); + } + + void testHandleRequest_GetInvalidNodeInfo() { + DiscoInfoResponder testling(router_); + + boost::shared_ptr<DiscoInfo> query(new DiscoInfo()); + query->setNode("bar-node"); + channel_->onIQReceived(IQ::createRequest(IQ::Get, JID("foo@bar.com"), "id-1", query)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + boost::shared_ptr<Error> payload(channel_->iqs_[0]->getPayload<Error>()); + CPPUNIT_ASSERT(payload); + } + + private: + IQRouter* router_; + DummyIQChannel* channel_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoResponderTest); diff --git a/Swiften/Queries/Responders/UnitTest/Makefile.inc b/Swiften/Queries/Responders/UnitTest/Makefile.inc new file mode 100644 index 0000000..8f06682 --- /dev/null +++ b/Swiften/Queries/Responders/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Queries/Responders/UnitTest/DiscoInfoResponderTest.cpp diff --git a/Swiften/Queries/UnitTest/Makefile.inc b/Swiften/Queries/UnitTest/Makefile.inc new file mode 100644 index 0000000..265357f --- /dev/null +++ b/Swiften/Queries/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/Queries/UnitTest/RequestTest.cpp \ + Swiften/Queries/UnitTest/ResponderTest.cpp diff --git a/Swiften/Queries/UnitTest/RequestTest.cpp b/Swiften/Queries/UnitTest/RequestTest.cpp new file mode 100644 index 0000000..31c0603 --- /dev/null +++ b/Swiften/Queries/UnitTest/RequestTest.cpp @@ -0,0 +1,139 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Elements/Payload.h" + +using namespace Swift; + +class RequestTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RequestTest); + CPPUNIT_TEST(testSendGet); + CPPUNIT_TEST(testSendSet); + CPPUNIT_TEST(testHandleIQ); + CPPUNIT_TEST(testHandleIQ_InvalidID); + CPPUNIT_TEST(testHandleIQ_Error); + CPPUNIT_TEST_SUITE_END(); + + public: + class MyPayload : public Payload { + public: + MyPayload(const String& s = "") : text_(s) {} + String text_; + }; + + typedef GenericRequest<MyPayload> MyRequest; + + public: + RequestTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + payload_ = boost::shared_ptr<Payload>(new MyPayload("foo")); + responsePayload_ = boost::shared_ptr<Payload>(new MyPayload("bar")); + responsesReceived_ = 0; + errorsReceived_ = 0; + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testSendSet() { + MyRequest testling(IQ::Set, JID("foo@bar.com/baz"), payload_, router_); + testling.send(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), channel_->iqs_[0]->getTo()); + CPPUNIT_ASSERT_EQUAL(IQ::Set, channel_->iqs_[0]->getType()); + CPPUNIT_ASSERT_EQUAL(String("test-id"), channel_->iqs_[0]->getID()); + } + + void testSendGet() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.send(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + CPPUNIT_ASSERT_EQUAL(IQ::Get, channel_->iqs_[0]->getType()); + } + + void testHandleIQ() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createResponse("test-id")); + + CPPUNIT_ASSERT_EQUAL(1, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorsReceived_); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + } + + // FIXME: Doesn't test that it didn't handle the payload + void testHandleIQ_InvalidID() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createResponse("different-id")); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorsReceived_); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + } + + void testHandleIQ_Error() { + MyRequest testling(IQ::Get, JID("foo@bar.com/baz"), payload_, router_); + testling.onResponse.connect(boost::bind(&RequestTest::handleResponse, this, _1, _2)); + testling.send(); + + channel_->onIQReceived(createError("test-id")); + + CPPUNIT_ASSERT_EQUAL(0, responsesReceived_); + CPPUNIT_ASSERT_EQUAL(1, errorsReceived_); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel_->iqs_.size())); + } + + private: + void handleResponse(boost::shared_ptr<Payload> p, const boost::optional<Error>& e) { + if (e) { + ++errorsReceived_; + } + else { + boost::shared_ptr<MyPayload> payload(boost::dynamic_pointer_cast<MyPayload>(p)); + CPPUNIT_ASSERT(payload); + CPPUNIT_ASSERT_EQUAL(String("bar"), payload->text_); + ++responsesReceived_; + } + } + + boost::shared_ptr<IQ> createResponse(const String& id) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Result)); + iq->addPayload(responsePayload_); + iq->setID(id); + return iq; + } + + boost::shared_ptr<IQ> createError(const String& id) { + boost::shared_ptr<IQ> iq(new IQ(IQ::Error)); + iq->setID(id); + return iq; + } + + private: + IQRouter* router_; + DummyIQChannel* channel_; + boost::shared_ptr<Payload> payload_; + boost::shared_ptr<Payload> responsePayload_; + int responsesReceived_; + int errorsReceived_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RequestTest); diff --git a/Swiften/Queries/UnitTest/ResponderTest.cpp b/Swiften/Queries/UnitTest/ResponderTest.cpp new file mode 100644 index 0000000..5c758e4 --- /dev/null +++ b/Swiften/Queries/UnitTest/ResponderTest.cpp @@ -0,0 +1,132 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> + +#include "Swiften/Queries/Responder.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Queries/DummyIQChannel.h" +#include "Swiften/Elements/SoftwareVersion.h" + +using namespace Swift; + +class ResponderTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResponderTest); + CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testHandleIQ_Set); + CPPUNIT_TEST(testHandleIQ_Get); + CPPUNIT_TEST(testHandleIQ_Error); + CPPUNIT_TEST(testHandleIQ_Result); + CPPUNIT_TEST(testHandleIQ_NoPayload); + CPPUNIT_TEST_SUITE_END(); + + public: + ResponderTest() {} + + void setUp() { + channel_ = new DummyIQChannel(); + router_ = new IQRouter(channel_); + payload_ = boost::shared_ptr<SoftwareVersion>(new SoftwareVersion("foo")); + } + + void tearDown() { + delete router_; + delete channel_; + } + + void testConstructor() { + MyResponder testling(router_); + + channel_->onIQReceived(createRequest(IQ::Set)); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_Set() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Set))); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.setPayloads_.size())); + CPPUNIT_ASSERT(payload_ == testling.setPayloads_[0]); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + } + + void testHandleIQ_Get() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Get))); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + CPPUNIT_ASSERT(payload_ == testling.getPayloads_[0]); + } + + void testHandleIQ_Error() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Error))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_Result() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(createRequest(IQ::Result))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + void testHandleIQ_NoPayload() { + MyResponder testling(router_); + + CPPUNIT_ASSERT(!dynamic_cast<IQHandler*>(&testling)->handleIQ(boost::shared_ptr<IQ>(new IQ(IQ::Get)))); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.getPayloads_.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(testling.setPayloads_.size())); + } + + private: + boost::shared_ptr<IQ> createRequest(IQ::Type type) { + boost::shared_ptr<IQ> iq(new IQ(type)); + iq->addPayload(payload_); + iq->setID("myid"); + iq->setFrom(JID("foo@bar.com/baz")); + return iq; + } + + private: + class MyResponder : public Responder<SoftwareVersion> { + public: + MyResponder(IQRouter* router) : Responder<SoftwareVersion>(router), getRequestResponse_(true), setRequestResponse_(true) {} + + virtual bool handleGetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload) { + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), from); + CPPUNIT_ASSERT_EQUAL(String("myid"), id); + getPayloads_.push_back(payload); + return getRequestResponse_; + } + virtual bool handleSetRequest(const JID& from, const String& id, boost::shared_ptr<SoftwareVersion> payload) { + CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/baz"), from); + CPPUNIT_ASSERT_EQUAL(String("myid"), id); + setPayloads_.push_back(payload); + return setRequestResponse_; + } + + bool getRequestResponse_; + bool setRequestResponse_; + std::vector<boost::shared_ptr<SoftwareVersion> > getPayloads_; + std::vector<boost::shared_ptr<SoftwareVersion> > setPayloads_; + }; + + private: + IQRouter* router_; + DummyIQChannel* channel_; + boost::shared_ptr<SoftwareVersion> payload_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResponderTest); diff --git a/Swiften/Roster/ContactRosterItem.cpp b/Swiften/Roster/ContactRosterItem.cpp new file mode 100644 index 0000000..f38b0f7 --- /dev/null +++ b/Swiften/Roster/ContactRosterItem.cpp @@ -0,0 +1,51 @@ +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/GroupRosterItem.h" + +namespace Swift { + + +ContactRosterItem::ContactRosterItem(const JID& jid, const String& name, GroupRosterItem* parent, TreeWidgetFactory* factory) : jid_(jid), name_(name) { + parent->addChild(this); + widget_ = factory->createTreeWidgetItem(parent->getWidget()); + widget_->setText(name.isEmpty() ? jid.toString() : name); + widget_->onUserAction.connect(boost::bind(&ContactRosterItem::handleUserAction, this, _1)); + setStatusShow(StatusShow::None); +} + +ContactRosterItem::~ContactRosterItem() { + delete widget_; +} + +StatusShow::Type ContactRosterItem::getStatusShow() { + return statusShow_; +} + +void ContactRosterItem::setStatusShow(StatusShow::Type show) { + statusShow_ = show; + int colour = 0; + switch (show) { + case StatusShow::Online: colour = 0x000000;break; + case StatusShow::Away: colour = 0x336699;break; + case StatusShow::XA: colour = 0x336699;break; + case StatusShow::FFC: colour = 0x000000;break; + case StatusShow::DND: colour = 0x990000;break; + case StatusShow::None: colour = 0x7F7F7F;break; + } + widget_->setTextColor(colour); +} + +const JID& ContactRosterItem::getJID() const { + return jid_; +} + +void ContactRosterItem::show() { + widget_->show(); +} + +void ContactRosterItem::hide() { + widget_->hide(); +} + +} + + diff --git a/Swiften/Roster/ContactRosterItem.h b/Swiften/Roster/ContactRosterItem.h new file mode 100644 index 0000000..20f9f65 --- /dev/null +++ b/Swiften/Roster/ContactRosterItem.h @@ -0,0 +1,39 @@ +#ifndef SWIFTEN_ContactRosterItem_H +#define SWIFTEN_ContactRosterItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/UserRosterAction.h" +#include "Swiften/Elements/StatusShow.h" + +#include <boost/bind.hpp> +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetItem; +class GroupRosterItem; +class ContactRosterItem : public RosterItem { + public: + ContactRosterItem(const JID& jid, const String& name, GroupRosterItem* parent, TreeWidgetFactory* factory); + ~ContactRosterItem(); + + StatusShow::Type getStatusShow(); + void setStatusShow(StatusShow::Type show); + const JID& getJID() const; + void show(); + void hide(); + + private: + JID jid_; + String name_; + TreeWidgetItem *widget_; + StatusShow::Type statusShow_; +}; + +} +#endif + diff --git a/Swiften/Roster/GroupRosterItem.h b/Swiften/Roster/GroupRosterItem.h new file mode 100644 index 0000000..f96a868 --- /dev/null +++ b/Swiften/Roster/GroupRosterItem.h @@ -0,0 +1,70 @@ +#ifndef SWIFTEN_GroupRosterItem_H +#define SWIFTEN_GroupRosterItem_H + +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Base/String.h" +#include "Swiften/Roster/TreeWidget.h" +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/TreeWidgetItem.h" +#include "Swiften/Roster/ContactRosterItem.h" + +#include <list> + +namespace Swift { + +class GroupRosterItem : public RosterItem { + public: + GroupRosterItem(const String& name, TreeWidget* tree, TreeWidgetFactory* factory) : name_(name) { + widget_ = factory->createTreeWidgetItem(tree); + widget_->setExpanded(true); + widget_->setText(name); + widget_->setTextColor(0xFFFFFF); + widget_->setBackgroundColor(0x969696); + } + + ~GroupRosterItem() { + delete widget_; + } + + const String& getName() const { + return name_; + } + + TreeWidgetItem* getWidget() const { + return widget_; + } + + const std::list<RosterItem*>& getChildren() const { + return children_; + } + + void addChild(RosterItem* item) { + children_.push_back(item); + } + + void removeChild(const JID& jid) { + std::list<RosterItem*>::iterator it = children_.begin(); + while (it != children_.end()) { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(*it); + if (contact && contact->getJID() == jid) { + delete contact; + it = children_.erase(it); + continue; + } + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(*it); + if (group) { + group->removeChild(jid); + } + it++; + } + } + + private: + String name_; + TreeWidgetItem* widget_; + std::list<RosterItem*> children_; +}; + +} +#endif + diff --git a/Swiften/Roster/Makefile.inc b/Swiften/Roster/Makefile.inc new file mode 100644 index 0000000..7c5b007 --- /dev/null +++ b/Swiften/Roster/Makefile.inc @@ -0,0 +1,6 @@ +SWIFTEN_SOURCES += \ + Swiften/Roster/ContactRosterItem.cpp \ + Swiften/Roster/Roster.cpp \ + Swiften/Roster/XMPPRoster.cpp + +include Swiften/Roster/UnitTest/Makefile.inc diff --git a/Swiften/Roster/OfflineRosterFilter.h b/Swiften/Roster/OfflineRosterFilter.h new file mode 100644 index 0000000..512d074 --- /dev/null +++ b/Swiften/Roster/OfflineRosterFilter.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_OfflineRosterFilter_H +#define SWIFTEN_OfflineRosterFilter_H + +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/RosterFilter.h" +#include "Swiften/Elements/StatusShow.h" + +namespace Swift { + +class OfflineRosterFilter : public RosterFilter { + public: + virtual ~OfflineRosterFilter() {} + virtual bool operator() (RosterItem *item) const { + ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(item); + return contactItem && contactItem->getStatusShow() == StatusShow::None; + } +}; + +} +#endif + + + diff --git a/Swiften/Roster/OpenChatRosterAction.h b/Swiften/Roster/OpenChatRosterAction.h new file mode 100644 index 0000000..03715a5 --- /dev/null +++ b/Swiften/Roster/OpenChatRosterAction.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_OpenChatRosterAction_H +#define SWIFTEN_OpenChatRosterAction_H + +#include "Swiften/Roster/UserRosterAction.h" + +namespace Swift { +class RosterItem; +class TreeWidgetItem; + +class OpenChatRosterAction : public UserRosterAction { + public: + virtual ~OpenChatRosterAction() {}; + +}; + +} +#endif + + + diff --git a/Swiften/Roster/Roster.cpp b/Swiften/Roster/Roster.cpp new file mode 100644 index 0000000..61c0286 --- /dev/null +++ b/Swiften/Roster/Roster.cpp @@ -0,0 +1,127 @@ +#include "Swiften/Roster/Roster.h" + +#include "Swiften/Base/foreach.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/ContactRosterItem.h" +#include "Swiften/Roster/RosterItem.h" +#include "Swiften/Roster/GroupRosterItem.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/TreeWidget.h" +#include "Swiften/Roster/TreeWidgetFactory.h" + +#include <boost/bind.hpp> + +#include <deque> + +namespace Swift { + +Roster::Roster(TreeWidget *treeWidget, TreeWidgetFactory *widgetFactory) : treeWidget_(treeWidget), widgetFactory_(widgetFactory) { +} + +Roster::~Roster() { + foreach (RosterItem* item, items_) { + delete item; + } + delete treeWidget_; +} + +TreeWidget* Roster::getWidget() { + return treeWidget_; +} + +GroupRosterItem* Roster::getGroup(const String& groupName) { + foreach (RosterItem *item, children_) { + GroupRosterItem *group = dynamic_cast<GroupRosterItem*>(item); + if (group && group->getName() == groupName) { + return group; + } + } + GroupRosterItem* group = new GroupRosterItem(groupName, treeWidget_, widgetFactory_); + children_.push_back(group); + items_.push_back(group); + return group; +} + +void Roster::handleUserAction(boost::shared_ptr<UserRosterAction> action) { + onUserAction(action); +} + +void Roster::addContact(const JID& jid, const String& name, const String& group) { + ContactRosterItem *item = new ContactRosterItem(jid, name, getGroup(group), widgetFactory_); + items_.push_back(item); + item->onUserAction.connect(boost::bind(&Roster::handleUserAction, this, _1)); + filterItem(item); + +} + +void Roster::removeContact(const JID& jid) { + std::vector<RosterItem*>::iterator it = children_.begin(); + while (it != children_.end()) { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(*it); + if (contact && contact->getJID() == jid) { + delete contact; + it = children_.erase(it); + continue; + } + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(*it); + if (group) { + group->removeChild(jid); + } + it++; + } +} + +void Roster::applyOnItems(const RosterItemOperation& operation) { + std::deque<RosterItem*> queue(children_.begin(), children_.end()); + while (!queue.empty()) { + RosterItem* item = *queue.begin(); + queue.pop_front(); + operation(item); + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + if (group) { + queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); + } + } + filterAll(); +} + +void Roster::removeFilter(RosterFilter *filter) { + for (unsigned int i = 0; i < filters_.size(); i++) { + if (filters_[i] == filter) { + filters_.erase(filters_.begin() + i); + break; + } + } + filterAll(); +} + + +void Roster::filterItem(RosterItem* rosterItem) { + ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(rosterItem); + if (!item) { + return; + } + bool hide = true; + foreach (RosterFilter *filter, filters_) { + hide &= (*filter)(item); + } + filters_.size() > 0 && hide ? item->hide() : item->show(); +} + +void Roster::filterAll() { + std::deque<RosterItem*> queue(children_.begin(), children_.end()); + while (!queue.empty()) { + RosterItem *item = *queue.begin(); + queue.pop_front(); + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + if (group) { + queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); + } else { + filterItem(item); + } + } +} + +} + diff --git a/Swiften/Roster/Roster.h b/Swiften/Roster/Roster.h new file mode 100644 index 0000000..cdd1407 --- /dev/null +++ b/Swiften/Roster/Roster.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_Roster_H +#define SWIFTEN_Roster_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/UserRosterAction.h" +#include "Swiften/Roster/RosterFilter.h" + +#include <vector> +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetFactory; +class TreeWidget; +class RosterItem; +class GroupRosterItem; + +class Roster |