diff options
author | Remko Tronçon <git@el-tramo.be> | 2009-06-01 08:48:42 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2009-06-01 09:24:28 (GMT) |
commit | 2812bddd81f8a1b804c7460f4e14cd0aa393d129 (patch) | |
tree | d46294f35150c4f0f43deaf2d31fceaf945ae715 /Swiften | |
download | swift-contrib-2812bddd81f8a1b804c7460f4e14cd0aa393d129.zip swift-contrib-2812bddd81f8a1b804c7460f4e14cd0aa393d129.tar.bz2 |
Import.
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 { + public: + Roster(TreeWidget *treeWidget, TreeWidgetFactory *widgetFactory); + ~Roster(); + + TreeWidget* getWidget(); + GroupRosterItem* getGroup(const String& groupName); + void addContact(const JID& jid, const String& name, const String& group); + void removeContact(const JID& jid); + void applyOnItems(const RosterItemOperation& operation); + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + void addFilter(RosterFilter *filter) {filters_.push_back(filter);filterAll();} + void removeFilter(RosterFilter *filter); + std::vector<RosterFilter*> getFilters() {return filters_;} + + private: + void filterItem(RosterItem* item); + void filterAll(); + void handleUserAction(boost::shared_ptr<UserRosterAction> action); + TreeWidget *treeWidget_; + TreeWidgetFactory *widgetFactory_; + std::vector<RosterItem*> children_; + std::vector<RosterItem*> items_; + std::vector<RosterFilter*> filters_; +}; +} + +#endif diff --git a/Swiften/Roster/RosterFilter.h b/Swiften/Roster/RosterFilter.h new file mode 100644 index 0000000..a824304 --- /dev/null +++ b/Swiften/Roster/RosterFilter.h @@ -0,0 +1,17 @@ +#ifndef SWIFTEN_RosterFilter_H +#define SWIFTEN_RosterFilter_H + +#include "Swiften/Roster/RosterItem.h" + +namespace Swift { + +class RosterFilter { + public: + virtual ~RosterFilter() {} + virtual bool operator() (RosterItem* item) const = 0; +}; + +} +#endif + + diff --git a/Swiften/Roster/RosterItem.h b/Swiften/Roster/RosterItem.h new file mode 100644 index 0000000..2707920 --- /dev/null +++ b/Swiften/Roster/RosterItem.h @@ -0,0 +1,24 @@ +#ifndef SWIFTEN_RosterItem_H +#define SWIFTEN_RosterItem_H + +#include "Swiften/Roster/UserRosterAction.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class RosterItem { + public: + virtual ~RosterItem() {}; + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + protected: + void handleUserAction(boost::shared_ptr<UserRosterAction> action) { + action->setRosterItem(this); + onUserAction(action); + } +}; + +} +#endif + diff --git a/Swiften/Roster/RosterItemOperation.h b/Swiften/Roster/RosterItemOperation.h new file mode 100644 index 0000000..ea8e723 --- /dev/null +++ b/Swiften/Roster/RosterItemOperation.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_RosterItemOperation_H +#define SWIFTEN_RosterItemOperation_H + +#include "Swiften/Roster/RosterItem.h" + +namespace Swift { + +class RosterItemOperation { + public: + virtual ~RosterItemOperation() {} + virtual void operator() (RosterItem*) const = 0; +}; + +} +#endif + diff --git a/Swiften/Roster/SetPresence.h b/Swiften/Roster/SetPresence.h new file mode 100644 index 0000000..aa36a52 --- /dev/null +++ b/Swiften/Roster/SetPresence.h @@ -0,0 +1,36 @@ +#ifndef SWIFTEN_SetPresence_H +#define SWIFTEN_SetPresence_H + +#include "Swiften/Elements/Presence.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Roster/RosterItemOperation.h" +#include "Swiften/Roster/ContactRosterItem.h" + +namespace Swift { + +class RosterItem; + +class SetPresence : public RosterItemOperation { + public: + SetPresence(boost::shared_ptr<Presence> presence, JID::CompareType compareType = JID::WithoutResource) : presence_(presence), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(presence_->getFrom(), compareType_)) { + if (presence_->getType() != Presence::Available) { + contact->setStatusShow(StatusShow::None); + } else { + contact->setStatusShow(presence_->getShow()); + } + } + } + + private: + boost::shared_ptr<Presence> presence_; + JID::CompareType compareType_; +}; + +} +#endif + diff --git a/Swiften/Roster/TreeWidget.h b/Swiften/Roster/TreeWidget.h new file mode 100644 index 0000000..a26003e --- /dev/null +++ b/Swiften/Roster/TreeWidget.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_TreeWidget_H +#define SWIFTEN_TreeWidget_H + +namespace Swift { + +class TreeWidget { + public: + virtual ~TreeWidget() {} +}; + +} +#endif + diff --git a/Swiften/Roster/TreeWidgetFactory.h b/Swiften/Roster/TreeWidgetFactory.h new file mode 100644 index 0000000..f4ba68d --- /dev/null +++ b/Swiften/Roster/TreeWidgetFactory.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_TreeWidgetFactory_H +#define SWIFTEN_TreeWidgetFactory_H + +namespace Swift { + +class TreeWidgetItem; +class TreeWidget; + +class TreeWidgetFactory { + public: + virtual ~TreeWidgetFactory() {} + virtual TreeWidget* createTreeWidget() = 0; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidgetItem* item) = 0; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidget* item) = 0; +}; + +} + +#endif + diff --git a/Swiften/Roster/TreeWidgetItem.h b/Swiften/Roster/TreeWidgetItem.h new file mode 100644 index 0000000..5a96a41 --- /dev/null +++ b/Swiften/Roster/TreeWidgetItem.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_TreeWidgetItem_H +#define SWIFTEN_TreeWidgetItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/Roster/UserRosterAction.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class TreeWidgetItem { + public: + virtual ~TreeWidgetItem() {} + virtual void setText(const String& text) = 0; + virtual void setExpanded(bool b) = 0; + virtual void setTextColor(unsigned long color) = 0; + virtual void setBackgroundColor(unsigned long color) = 0; + boost::signal<void (boost::shared_ptr<UserRosterAction>)> onUserAction; + virtual void show() = 0; + virtual void hide() = 0; + void performUserAction(boost::shared_ptr<UserRosterAction> action) { + action->setTreeWidgetItem(this); + onUserAction(action); + } +}; + +} +#endif + diff --git a/Swiften/Roster/UnitTest/Makefile.inc b/Swiften/Roster/UnitTest/Makefile.inc new file mode 100644 index 0000000..5631641 --- /dev/null +++ b/Swiften/Roster/UnitTest/Makefile.inc @@ -0,0 +1,4 @@ +UNITTEST_SOURCES += \ + Swiften/Roster/UnitTest/RosterTest.cpp \ + Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp + diff --git a/Swiften/Roster/UnitTest/MockTreeWidget.h b/Swiften/Roster/UnitTest/MockTreeWidget.h new file mode 100644 index 0000000..e6f6def --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidget.h @@ -0,0 +1,14 @@ +#ifndef SWIFTEN_MockTreeWidget_H +#define SWIFTEN_MockTreeWidget_H + +#include "Swiften/Roster/TreeWidget.h" + +namespace Swift { + +class MockTreeWidget : public TreeWidget { + public: + virtual ~MockTreeWidget() {} +}; + +} +#endif diff --git a/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h b/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h new file mode 100644 index 0000000..09e4742 --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidgetFactory.h @@ -0,0 +1,31 @@ +#ifndef SWIFTEN_MockTreeWidgetFactory_H +#define SWIFTEN_MockTreeWidgetFactory_H + +#include "Swiften/Roster/TreeWidgetFactory.h" +#include "Swiften/Roster/UnitTest/MockTreeWidget.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetItem.h" + +namespace Swift { + +class MockTreeWidgetItem; +class MockTreeWidget; + +class MockTreeWidgetFactory : public TreeWidgetFactory { + public: + virtual ~MockTreeWidgetFactory() {} + virtual TreeWidget* createTreeWidget() { + return new MockTreeWidget(); + }; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidgetItem*) { + return new MockTreeWidgetItem(); + }; + virtual TreeWidgetItem* createTreeWidgetItem(TreeWidget*) { + return new MockTreeWidgetItem(); + } +}; + +} + +#endif + + diff --git a/Swiften/Roster/UnitTest/MockTreeWidgetItem.h b/Swiften/Roster/UnitTest/MockTreeWidgetItem.h new file mode 100644 index 0000000..f352936 --- /dev/null +++ b/Swiften/Roster/UnitTest/MockTreeWidgetItem.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_MockTreeWidgetItem_H +#define SWIFTEN_MockTreeWidgetItem_H + +#include "Swiften/Base/String.h" +#include "Swiften/Roster/TreeWidgetItem.h" + +#include <boost/signal.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class MockTreeWidgetItem : public TreeWidgetItem { + public: + virtual ~MockTreeWidgetItem() {}; + virtual void setText(const String&) {}; + virtual void setExpanded(bool) {}; + virtual void setTextColor(unsigned long) {}; + virtual void setBackgroundColor(unsigned long) {}; + virtual void show() {}; + virtual void hide() {}; +}; + +} +#endif + + diff --git a/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp b/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Swiften/Roster/UnitTest/OfflineRosterFilterTest.cpp diff --git a/Swiften/Roster/UnitTest/RosterTest.cpp b/Swiften/Roster/UnitTest/RosterTest.cpp new file mode 100644 index 0000000..b43a41c --- /dev/null +++ b/Swiften/Roster/UnitTest/RosterTest.cpp @@ -0,0 +1,57 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Roster/Roster.h" +#include "Swiften/Roster/UnitTest/MockTreeWidget.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetFactory.h" +#include "Swiften/Roster/UnitTest/MockTreeWidgetItem.h" + +using namespace Swift; + +class RosterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterTest); + CPPUNIT_TEST(testGetGroup); + CPPUNIT_TEST_SUITE_END(); + + private: + Roster *roster_; + TreeWidget *widget_; + TreeWidgetFactory *factory_; + JID jid1_; + JID jid2_; + JID jid3_; + + public: + + RosterTest() : jid1_(JID("a@b.c")), jid2_(JID("b@c.d")), jid3_(JID("c@d.e")) {} + + void setUp() { + factory_ = new MockTreeWidgetFactory(); + widget_ = factory_->createTreeWidget(); + roster_ = new Roster(widget_, factory_); + } + + void tearDown() { + delete roster_; + //delete widget_; + delete factory_; + } + + void testGetGroup() { + roster_->addContact(jid1_, "Bert", "group1"); + roster_->addContact(jid2_, "Ernie", "group2"); + roster_->addContact(jid3_, "Cookie", "group1"); + + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group1"), roster_->getGroup("group1")); + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group2"), roster_->getGroup("group2")); + CPPUNIT_ASSERT_EQUAL(roster_->getGroup("group3"), roster_->getGroup("group3")); + CPPUNIT_ASSERT(roster_->getGroup("group1") != roster_->getGroup("group2")); + CPPUNIT_ASSERT(roster_->getGroup("group2") != roster_->getGroup("group3")); + CPPUNIT_ASSERT(roster_->getGroup("group3") != roster_->getGroup("group1")); + } + +}; +CPPUNIT_TEST_SUITE_REGISTRATION(RosterTest); + diff --git a/Swiften/Roster/UserRosterAction.h b/Swiften/Roster/UserRosterAction.h new file mode 100644 index 0000000..80ace68 --- /dev/null +++ b/Swiften/Roster/UserRosterAction.h @@ -0,0 +1,32 @@ +#ifndef SWIFTEN_UserRosterAction_H +#define SWIFTEN_UserRosterAction_H + +namespace Swift { +class RosterItem; +class TreeWidgetItem; + +class UserRosterAction { + public: + virtual ~UserRosterAction() {}; + void setRosterItem(RosterItem *item) { + rosterItem_ = item; + }; + void setTreeWidgetItem(TreeWidgetItem *item) { + treeWidgetItem_ = item; + } + RosterItem* getRosterItem() { + return rosterItem_; + } + TreeWidgetItem* getTreeWidgetItem() { + return treeWidgetItem_; + } + + private: + RosterItem *rosterItem_; + TreeWidgetItem *treeWidgetItem_; +}; + +} +#endif + + diff --git a/Swiften/Roster/XMPPRoster.cpp b/Swiften/Roster/XMPPRoster.cpp new file mode 100644 index 0000000..9661171 --- /dev/null +++ b/Swiften/Roster/XMPPRoster.cpp @@ -0,0 +1,37 @@ +#include "Swiften/Roster/XMPPRoster.h" + +namespace Swift { + +void XMPPRoster::addContact(const JID& jid, const String& name, const std::vector<String>& groups) { + JID bareJID(jid.toBare()); + bool exists = containsJID(bareJID); + if (exists) { + entries_.erase(bareJID); + } + entries_[bareJID] = std::pair<String, std::vector<String> >(name, groups); + if (exists) { + onJIDUpdated(bareJID); + } else { + onJIDAdded(bareJID); + } +} + +void XMPPRoster::removeContact(const JID& jid) { + entries_.erase(JID(jid.toBare())); + onJIDRemoved(jid); +} + +bool XMPPRoster::containsJID(const JID& jid) { + return entries_.find(JID(jid.toBare())) != entries_.end(); +} + +const String& XMPPRoster::getNameForJID(const JID& jid) { + return entries_[JID(jid.toBare())].first; +} + +const std::vector<String>& XMPPRoster::getGroupsForJID(const JID& jid) { + return entries_[JID(jid.toBare())].second; +} + +} + diff --git a/Swiften/Roster/XMPPRoster.h b/Swiften/Roster/XMPPRoster.h new file mode 100644 index 0000000..f2afbb3 --- /dev/null +++ b/Swiften/Roster/XMPPRoster.h @@ -0,0 +1,34 @@ +#ifndef SWIFTEN_XMPPRoster_H +#define SWIFTEN_XMPPRoster_H + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" + +#include <map> +#include <vector> +#include <boost/signal.hpp> + +namespace Swift { + +class XMPPRoster { + public: + XMPPRoster() {}; + ~XMPPRoster() {}; + + void addContact(const JID& jid, const String& name, const std::vector<String>& groups); + bool containsJID(const JID& jid); + void removeContact(const JID& jid); + const String& getNameForJID(const JID& jid); + const std::vector<String>& getGroupsForJID(const JID& jid); + + boost::signal<void (const JID&)> onJIDAdded; + boost::signal<void (const JID&)> onJIDRemoved; + boost::signal<void (const JID&)> onJIDUpdated; + + private: + std::map<JID, std::pair<String, std::vector<String> > > entries_; +}; +} + +#endif + diff --git a/Swiften/SASL/Makefile.inc b/Swiften/SASL/Makefile.inc new file mode 100644 index 0000000..e6c658f --- /dev/null +++ b/Swiften/SASL/Makefile.inc @@ -0,0 +1,4 @@ +SWIFTEN_SOURCES += \ + Swiften/SASL/PLAINMessage.cpp + +include Swiften/SASL/UnitTest/Makefile.inc diff --git a/Swiften/SASL/PLAINMessage.cpp b/Swiften/SASL/PLAINMessage.cpp new file mode 100644 index 0000000..c88d6cf --- /dev/null +++ b/Swiften/SASL/PLAINMessage.cpp @@ -0,0 +1,10 @@ +#include "Swiften/SASL/PLAINMessage.h" + +namespace Swift { + +PLAINMessage::PLAINMessage(const String& authcid, const String& password, const String& authzid) { + String s = authzid + '\0' + authcid + '\0' + password; + value_ = ByteArray(s.getUTF8Data(), s.getUTF8Size()); +} + +} diff --git a/Swiften/SASL/PLAINMessage.h b/Swiften/SASL/PLAINMessage.h new file mode 100644 index 0000000..14a51f2 --- /dev/null +++ b/Swiften/SASL/PLAINMessage.h @@ -0,0 +1,22 @@ +#ifndef SASL_PLAINMESSAGE_H +#define SASL_PLAINMESSAGE_H + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PLAINMessage + { + public: + PLAINMessage(const String& authcid, const String& password, const String& authzid = ""); + + const ByteArray& getValue() { + return value_; + } + + private: + ByteArray value_; + }; +} + +#endif diff --git a/Swiften/SASL/UnitTest/Makefile.inc b/Swiften/SASL/UnitTest/Makefile.inc new file mode 100644 index 0000000..e985898 --- /dev/null +++ b/Swiften/SASL/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/SASL/UnitTest/PLAINMessageTest.cpp diff --git a/Swiften/SASL/UnitTest/PLAINMessageTest.cpp b/Swiften/SASL/UnitTest/PLAINMessageTest.cpp new file mode 100644 index 0000000..da9287a --- /dev/null +++ b/Swiften/SASL/UnitTest/PLAINMessageTest.cpp @@ -0,0 +1,29 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/SASL/PLAINMessage.h" + +using namespace Swift; + +class PLAINMessageTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PLAINMessageTest); + CPPUNIT_TEST(testConstructor_WithoutAuthzID); + CPPUNIT_TEST(testConstructor_WithAuthzID); + CPPUNIT_TEST_SUITE_END(); + + public: + PLAINMessageTest() {} + + void testConstructor_WithoutAuthzID() { + PLAINMessage message("user", "pass"); + CPPUNIT_ASSERT_EQUAL(message.getValue(), ByteArray("\0user\0pass", 10)); + } + + void testConstructor_WithAuthzID() { + PLAINMessage message("user", "pass", "authz"); + CPPUNIT_ASSERT_EQUAL(message.getValue(), ByteArray("authz\0user\0pass", 15)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PLAINMessageTest); diff --git a/Swiften/Serializer/AuthFailureSerializer.h b/Swiften/Serializer/AuthFailureSerializer.h new file mode 100644 index 0000000..e7e2ced --- /dev/null +++ b/Swiften/Serializer/AuthFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_AuthFailureSerializer_H +#define SWIFTEN_AuthFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class AuthFailureSerializer : public GenericElementSerializer<AuthFailure> { + public: + AuthFailureSerializer() : GenericElementSerializer<AuthFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-sasl").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/AuthRequestSerializer.cpp b/Swiften/Serializer/AuthRequestSerializer.cpp new file mode 100644 index 0000000..7122485 --- /dev/null +++ b/Swiften/Serializer/AuthRequestSerializer.cpp @@ -0,0 +1,17 @@ +#include "Swiften/Serializer/AuthRequestSerializer.h" + +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +AuthRequestSerializer::AuthRequestSerializer() { +} + +String AuthRequestSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<AuthRequest> authRequest(boost::dynamic_pointer_cast<AuthRequest>(element)); + String value = (authRequest->getMessage().isEmpty() ? "=" : Base64::encode(authRequest->getMessage())); + return "<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"" + authRequest->getMechanism() + "\">" + value + "</auth>"; +} + +} diff --git a/Swiften/Serializer/AuthRequestSerializer.h b/Swiften/Serializer/AuthRequestSerializer.h new file mode 100644 index 0000000..b5131a3 --- /dev/null +++ b/Swiften/Serializer/AuthRequestSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_AuthRequestSerializer_H +#define SWIFTEN_AuthRequestSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthRequest.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class AuthRequestSerializer : public GenericElementSerializer<AuthRequest> { + public: + AuthRequestSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/AuthSuccessSerializer.h b/Swiften/Serializer/AuthSuccessSerializer.h new file mode 100644 index 0000000..1fdc3ab --- /dev/null +++ b/Swiften/Serializer/AuthSuccessSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_AuthSuccessSerializer_H +#define SWIFTEN_AuthSuccessSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/AuthSuccess.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class AuthSuccessSerializer : public GenericElementSerializer<AuthSuccess> { + public: + AuthSuccessSerializer() : GenericElementSerializer<AuthSuccess>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("success", "urn:ietf:params:xml:ns:xmpp-sasl").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/CompressFailureSerializer.h b/Swiften/Serializer/CompressFailureSerializer.h new file mode 100644 index 0000000..c3e7953 --- /dev/null +++ b/Swiften/Serializer/CompressFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_CompressFailureSerializer_H +#define SWIFTEN_CompressFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/CompressFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class CompressFailureSerializer : public GenericElementSerializer<CompressFailure> { + public: + CompressFailureSerializer() : GenericElementSerializer<CompressFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "http://jabber.org/protocol/compress").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/CompressRequestSerializer.cpp b/Swiften/Serializer/CompressRequestSerializer.cpp new file mode 100644 index 0000000..4d3310b --- /dev/null +++ b/Swiften/Serializer/CompressRequestSerializer.cpp @@ -0,0 +1,19 @@ +#include "Swiften/Serializer/CompressRequestSerializer.h" + +#include "Swiften/Elements/CompressRequest.h" + +namespace Swift { + +CompressRequestSerializer::CompressRequestSerializer() { +} + +String CompressRequestSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<CompressRequest> compressRequest(boost::dynamic_pointer_cast<CompressRequest>(element)); + return "<compress xmlns='http://jabber.org/protocol/compress'><method>" + compressRequest->getMethod() + "</method></compress>"; +} + +bool CompressRequestSerializer::canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<CompressRequest*>(element.get()) != 0; +} + +} diff --git a/Swiften/Serializer/CompressRequestSerializer.h b/Swiften/Serializer/CompressRequestSerializer.h new file mode 100644 index 0000000..b6a493c --- /dev/null +++ b/Swiften/Serializer/CompressRequestSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_COMPRESSREQUESTSERIALIZER_H +#define SWIFTEN_COMPRESSREQUESTSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + class CompressRequestSerializer : public ElementSerializer { + public: + CompressRequestSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + virtual bool canSerialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/ElementSerializer.cpp b/Swiften/Serializer/ElementSerializer.cpp new file mode 100644 index 0000000..22c64a6 --- /dev/null +++ b/Swiften/Serializer/ElementSerializer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + +ElementSerializer::~ElementSerializer() { +} + +} diff --git a/Swiften/Serializer/ElementSerializer.h b/Swiften/Serializer/ElementSerializer.h new file mode 100644 index 0000000..d2f5611 --- /dev/null +++ b/Swiften/Serializer/ElementSerializer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_ELEMENTSERIALIZER_H +#define SWIFTEN_ELEMENTSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Element.h" + +namespace Swift { + class ElementSerializer { + public: + virtual ~ElementSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const = 0; + virtual bool canSerialize(boost::shared_ptr<Element> element) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/GenericElementSerializer.h b/Swiften/Serializer/GenericElementSerializer.h new file mode 100644 index 0000000..7ccecb2 --- /dev/null +++ b/Swiften/Serializer/GenericElementSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_GenericElementSerializer_H +#define SWIFTEN_GenericElementSerializer_H + +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + template<typename T> + class GenericElementSerializer : public ElementSerializer { + public: + virtual String serialize(boost::shared_ptr<Element> element) const = 0; + + virtual bool canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<T*>(element.get()) != 0; + } + }; +} + +#endif diff --git a/Swiften/Serializer/GenericPayloadSerializer.h b/Swiften/Serializer/GenericPayloadSerializer.h new file mode 100644 index 0000000..c4a45c3 --- /dev/null +++ b/Swiften/Serializer/GenericPayloadSerializer.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_GenericPayloadSerializer_H +#define SWIFTEN_GenericPayloadSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Elements/Body.h" + +namespace Swift { + template<typename PAYLOAD_TYPE> + class GenericPayloadSerializer : public PayloadSerializer { + public: + virtual String serialize(boost::shared_ptr<Payload> element) const { + return serializePayload(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(element)); + } + + virtual bool canSerialize(boost::shared_ptr<Payload> element) const { + return dynamic_cast<PAYLOAD_TYPE*>(element.get()); + } + + virtual String serializePayload(boost::shared_ptr<PAYLOAD_TYPE>) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/GenericStanzaSerializer.h b/Swiften/Serializer/GenericStanzaSerializer.h new file mode 100644 index 0000000..c59f735 --- /dev/null +++ b/Swiften/Serializer/GenericStanzaSerializer.h @@ -0,0 +1,29 @@ +#ifndef SWIFTEN_GENERICSTANZASERIALIZER_H +#define SWIFTEN_GENERICSTANZASERIALIZER_H + +#include "Swiften/Serializer/StanzaSerializer.h" + +namespace Swift { + template<typename STANZA_TYPE> + class GenericStanzaSerializer : public StanzaSerializer { + public: + GenericStanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers) : StanzaSerializer(tag, payloadSerializers) {} + + virtual bool canSerialize(boost::shared_ptr<Element> element) const { + return dynamic_cast<STANZA_TYPE*>(element.get()) != 0; + } + + virtual void setStanzaSpecificAttributes( + boost::shared_ptr<Element> stanza, + XMLElement& element) const { + setStanzaSpecificAttributesGeneric( + boost::dynamic_pointer_cast<STANZA_TYPE>(stanza), element); + } + + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<STANZA_TYPE>, + XMLElement&) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/IQSerializer.h b/Swiften/Serializer/IQSerializer.h new file mode 100644 index 0000000..3623f24 --- /dev/null +++ b/Swiften/Serializer/IQSerializer.h @@ -0,0 +1,30 @@ +#ifndef SWIFTEN_IQSerializer_H +#define SWIFTEN_IQSerializer_H + +#include <cassert> + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/IQ.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class IQSerializer : public GenericStanzaSerializer<IQ> { + public: + IQSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<IQ>("iq", payloadSerializers) {} + + private: + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<IQ> iq, + XMLElement& element) const { + switch (iq->getType()) { + case IQ::Get: element.setAttribute("type","get"); break; + case IQ::Set: element.setAttribute("type","set"); break; + case IQ::Result: element.setAttribute("type","result"); break; + case IQ::Error: element.setAttribute("type","error"); break; + } + } + }; +} + +#endif diff --git a/Swiften/Serializer/Makefile.inc b/Swiften/Serializer/Makefile.inc new file mode 100644 index 0000000..5fbe57a --- /dev/null +++ b/Swiften/Serializer/Makefile.inc @@ -0,0 +1,15 @@ +SWIFTEN_SOURCES += \ + Swiften/Serializer/ElementSerializer.cpp \ + Swiften/Serializer/CompressRequestSerializer.cpp \ + Swiften/Serializer/AuthRequestSerializer.cpp \ + Swiften/Serializer/StreamFeaturesSerializer.cpp \ + Swiften/Serializer/XMPPSerializer.cpp \ + Swiften/Serializer/StanzaSerializer.cpp \ + Swiften/Serializer/PresenceSerializer.cpp \ + Swiften/Serializer/MessageSerializer.cpp \ + Swiften/Serializer/PayloadSerializer.cpp \ + Swiften/Serializer/PayloadSerializerCollection.cpp + +include Swiften/Serializer/UnitTest/Makefile.inc +include Swiften/Serializer/XML/Makefile.inc +include Swiften/Serializer/PayloadSerializers/Makefile.inc diff --git a/Swiften/Serializer/MessageSerializer.cpp b/Swiften/Serializer/MessageSerializer.cpp new file mode 100644 index 0000000..a3cf2ad --- /dev/null +++ b/Swiften/Serializer/MessageSerializer.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Serializer/MessageSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +MessageSerializer::MessageSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<Message>("message", payloadSerializers) { +} + +void MessageSerializer::setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Message> message, + XMLElement& element) const { + if (message->getType() == Message::Chat) { + element.setAttribute("type", "chat"); + } + else if (message->getType() == Message::Groupchat) { + element.setAttribute("type", "groupchat"); + } + else if (message->getType() == Message::Headline) { + element.setAttribute("type", "headline"); + } + else if (message->getType() == Message::Error) { + element.setAttribute("type", "error"); + } +} + +} diff --git a/Swiften/Serializer/MessageSerializer.h b/Swiften/Serializer/MessageSerializer.h new file mode 100644 index 0000000..8cb32c7 --- /dev/null +++ b/Swiften/Serializer/MessageSerializer.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_MessageSerializer_H +#define SWIFTEN_MessageSerializer_H + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/Message.h" + +namespace Swift { + class XMLElement; + + class MessageSerializer : public GenericStanzaSerializer<Message> { + public: + MessageSerializer(PayloadSerializerCollection* payloadSerializers); + + private: + void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Message> message, + XMLElement& element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializer.cpp new file mode 100644 index 0000000..47295ac --- /dev/null +++ b/Swiften/Serializer/PayloadSerializer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/PayloadSerializer.h" + +namespace Swift { + +PayloadSerializer::~PayloadSerializer() { +} + +} diff --git a/Swiften/Serializer/PayloadSerializer.h b/Swiften/Serializer/PayloadSerializer.h new file mode 100644 index 0000000..935ff4b --- /dev/null +++ b/Swiften/Serializer/PayloadSerializer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_PAYLOADSERIALIZER_H +#define SWIFTEN_PAYLOADSERIALIZER_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class PayloadSerializer { + public: + virtual ~PayloadSerializer(); + + virtual bool canSerialize(boost::shared_ptr<Payload>) const = 0; + virtual String serialize(boost::shared_ptr<Payload>) const = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializerCollection.cpp new file mode 100644 index 0000000..db86eea --- /dev/null +++ b/Swiften/Serializer/PayloadSerializerCollection.cpp @@ -0,0 +1,27 @@ +#include <boost/bind.hpp> +#include <algorithm> + +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Serializer/PayloadSerializer.h" + +namespace Swift { + +PayloadSerializerCollection::PayloadSerializerCollection() { +} + +void PayloadSerializerCollection::addSerializer(PayloadSerializer* serializer) { + serializers_.push_back(serializer); +} + +void PayloadSerializerCollection::removeSerializer(PayloadSerializer* serializer) { + serializers_.erase(remove(serializers_.begin(), serializers_.end(), serializer), serializers_.end()); +} + +PayloadSerializer* PayloadSerializerCollection::getPayloadSerializer(boost::shared_ptr<Payload> payload) const { + std::vector<PayloadSerializer*>::const_iterator i = std::find_if( + serializers_.begin(), serializers_.end(), + boost::bind(&PayloadSerializer::canSerialize, _1, payload)); + return (i != serializers_.end() ? *i : NULL); +} + +} diff --git a/Swiften/Serializer/PayloadSerializerCollection.h b/Swiften/Serializer/PayloadSerializerCollection.h new file mode 100644 index 0000000..a126989 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializerCollection.h @@ -0,0 +1,26 @@ +#ifndef SWIFTEN_PAYLOADSERIALIZERCOLLECTION_H +#define SWIFTEN_PAYLOADSERIALIZERCOLLECTION_H + +#include <vector> +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class PayloadSerializer; + class String; + + class PayloadSerializerCollection { + public: + PayloadSerializerCollection(); + + void addSerializer(PayloadSerializer* factory); + void removeSerializer(PayloadSerializer* factory); + PayloadSerializer* getPayloadSerializer(boost::shared_ptr<Payload>) const; + + private: + std::vector<PayloadSerializer*> serializers_; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/BodySerializer.h b/Swiften/Serializer/PayloadSerializers/BodySerializer.h new file mode 100644 index 0000000..7fba561 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/BodySerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_BodySerializer_H +#define SWIFTEN_BodySerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Elements/Body.h" + +namespace Swift { + class BodySerializer : public GenericPayloadSerializer<Body> { + public: + BodySerializer() : GenericPayloadSerializer<Body>() {} + + virtual String serializePayload(boost::shared_ptr<Body> body) const { + XMLTextNode textNode(body->getText()); + return "<body>" + textNode.serialize() + "</body>"; + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp new file mode 100644 index 0000000..fbb72af --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp @@ -0,0 +1,20 @@ +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +CapsInfoSerializer::CapsInfoSerializer() : GenericPayloadSerializer<CapsInfo>() { +} + +String CapsInfoSerializer::serializePayload(boost::shared_ptr<CapsInfo> capsInfo) const { + XMLElement capsElement("c", "http://jabber.org/protocol/caps"); + capsElement.setAttribute("node", capsInfo->getNode()); + capsElement.setAttribute("hash", capsInfo->getHash()); + capsElement.setAttribute("ver", capsInfo->getVersion()); + return capsElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h new file mode 100644 index 0000000..cb4fa33 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_CapsInfoSerializer_H +#define SWIFTEN_CapsInfoSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/CapsInfo.h" + +namespace Swift { + class CapsInfoSerializer : public GenericPayloadSerializer<CapsInfo> { + public: + CapsInfoSerializer(); + + virtual String serializePayload(boost::shared_ptr<CapsInfo>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp new file mode 100644 index 0000000..4c39574 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp @@ -0,0 +1,36 @@ +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +DiscoInfoSerializer::DiscoInfoSerializer() : GenericPayloadSerializer<DiscoInfo>() { +} + +String DiscoInfoSerializer::serializePayload(boost::shared_ptr<DiscoInfo> discoInfo) const { + XMLElement queryElement("query", "http://jabber.org/protocol/disco#info"); + if (!discoInfo->getNode().isEmpty()) { + queryElement.setAttribute("node", discoInfo->getNode()); + } + foreach(const DiscoInfo::Identity& identity, discoInfo->getIdentities()) { + boost::shared_ptr<XMLElement> identityElement(new XMLElement("identity")); + if (!identity.getLanguage().isEmpty()) { + identityElement->setAttribute("xml:lang", identity.getLanguage()); + } + identityElement->setAttribute("category", identity.getCategory()); + identityElement->setAttribute("name", identity.getName()); + identityElement->setAttribute("type", identity.getType()); + queryElement.addNode(identityElement); + } + foreach(const String& feature, discoInfo->getFeatures()) { + boost::shared_ptr<XMLElement> featureElement(new XMLElement("feature")); + featureElement->setAttribute("var", feature); + queryElement.addNode(featureElement); + } + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h new file mode 100644 index 0000000..929a455 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_DiscoInfoSerializer_H +#define SWIFTEN_DiscoInfoSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/DiscoInfo.h" + +namespace Swift { + class DiscoInfoSerializer : public GenericPayloadSerializer<DiscoInfo> { + public: + DiscoInfoSerializer(); + + virtual String serializePayload(boost::shared_ptr<DiscoInfo>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp new file mode 100644 index 0000000..347e1a5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp @@ -0,0 +1,56 @@ +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +ErrorSerializer::ErrorSerializer() : GenericPayloadSerializer<Error>() { +} + +String ErrorSerializer::serializePayload(boost::shared_ptr<Error> error) const { + String result("<error type=\""); + switch (error->getType()) { + case Error::Continue: result += "continue"; break; + case Error::Modify: result += "modify"; break; + case Error::Auth: result += "auth"; break; + case Error::Wait: result += "wait"; break; + default: result += "cancel"; break; + } + result += "\">"; + + String conditionElement; + switch (error->getCondition()) { + case Error::BadRequest: conditionElement = "bad-request"; break; + case Error::Conflict: conditionElement = "conflict"; break; + case Error::FeatureNotImplemented: conditionElement = "feature-not-implemented"; break; + case Error::Forbidden: conditionElement = "forbidden"; break; + case Error::Gone: conditionElement = "gone"; break; + case Error::InternalServerError: conditionElement = "internal-server-error"; break; + case Error::ItemNotFound: conditionElement = "item-not-found"; break; + case Error::JIDMalformed: conditionElement = "jid-malformed"; break; + case Error::NotAcceptable: conditionElement = "not-acceptable"; break; + case Error::NotAllowed: conditionElement = "not-allowed"; break; + case Error::NotAuthorized: conditionElement = "not-authorized"; break; + case Error::PaymentRequired: conditionElement = "payment-required"; break; + case Error::RecipientUnavailable: conditionElement = "recipient-unavailable"; break; + case Error::Redirect: conditionElement = "redirect"; break; + case Error::RegistrationRequired: conditionElement = "registration-required"; break; + case Error::RemoteServerNotFound: conditionElement = "remote-server-not-found"; break; + case Error::RemoteServerTimeout: conditionElement = "remote-server-timeout"; break; + case Error::ResourceConstraint: conditionElement = "resource-constraint"; break; + case Error::ServiceUnavailable: conditionElement = "service-unavailable"; break; + case Error::SubscriptionRequired: conditionElement = "subscription-required"; break; + case Error::UnexpectedRequest: conditionElement = "unexpected-request"; break; + default: conditionElement = "undefined-condition"; break; + } + result += "<" + conditionElement + " xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"; + + if (!error->getText().isEmpty()) { + XMLTextNode textNode(error->getText()); + result += "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">" + textNode.serialize() + "</text>"; + } + + result += "</error>"; + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h new file mode 100644 index 0000000..ecf73dc --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ErrorSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ErrorSerializer_H +#define SWIFTEN_ErrorSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/Error.h" + +namespace Swift { + class ErrorSerializer : public GenericPayloadSerializer<Error> { + public: + ErrorSerializer(); + + virtual String serializePayload(boost::shared_ptr<Error> error) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp new file mode 100644 index 0000000..9799802 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/BodySerializer.h" +#include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" + +namespace Swift { + +FullPayloadSerializerCollection::FullPayloadSerializerCollection() { + serializers_.push_back(new BodySerializer()); + serializers_.push_back(new PrioritySerializer()); + serializers_.push_back(new ErrorSerializer()); + serializers_.push_back(new RosterSerializer()); + serializers_.push_back(new MUCPayloadSerializer()); + serializers_.push_back(new SoftwareVersionSerializer()); + serializers_.push_back(new StatusSerializer()); + serializers_.push_back(new StatusShowSerializer()); + serializers_.push_back(new DiscoInfoSerializer()); + serializers_.push_back(new CapsInfoSerializer()); + serializers_.push_back(new ResourceBindSerializer()); + serializers_.push_back(new StartSessionSerializer()); + serializers_.push_back(new SecurityLabelSerializer()); + serializers_.push_back(new SecurityLabelsCatalogSerializer()); + foreach(PayloadSerializer* serializer, serializers_) { + addSerializer(serializer); + } +} + +FullPayloadSerializerCollection::~FullPayloadSerializerCollection() { + foreach(PayloadSerializer* serializer, serializers_) { + removeSerializer(serializer); + delete serializer; + } + serializers_.clear(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h new file mode 100644 index 0000000..e1655b5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_FULLPAYLOADSERIALIZERCOLLECTION_H +#define SWIFTEN_FULLPAYLOADSERIALIZERCOLLECTION_H + +#include <vector> + +#include "Swiften/Serializer/PayloadSerializerCollection.h" + +namespace Swift { + class FullPayloadSerializerCollection : public PayloadSerializerCollection { + public: + FullPayloadSerializerCollection(); + ~FullPayloadSerializerCollection(); + + private: + std::vector<PayloadSerializer*> serializers_; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp new file mode 100644 index 0000000..9c701cc --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp @@ -0,0 +1,13 @@ +#include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h" + +namespace Swift { + +MUCPayloadSerializer::MUCPayloadSerializer() : GenericPayloadSerializer<MUCPayload>() { +} + +String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload>) const { + String result("<x xmlns='http://jabber.org/protocol/muc'/>"); + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h new file mode 100644 index 0000000..504c969 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_MUCPayloadSerializer_H +#define SWIFTEN_MUCPayloadSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/MUCPayload.h" + +namespace Swift { + class MUCPayloadSerializer : public GenericPayloadSerializer<MUCPayload> { + public: + MUCPayloadSerializer(); + + virtual String serializePayload(boost::shared_ptr<MUCPayload> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/Makefile.inc b/Swiften/Serializer/PayloadSerializers/Makefile.inc new file mode 100644 index 0000000..61f603a --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/Makefile.inc @@ -0,0 +1,13 @@ +SWIFTEN_SOURCES += \ + Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp \ + Swiften/Serializer/PayloadSerializers/ErrorSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp \ + Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp + +include Swiften/Serializer/PayloadSerializers/UnitTest/Makefile.inc diff --git a/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h b/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h new file mode 100644 index 0000000..f132b6e --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/PrioritySerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_PrioritySerializer_H +#define SWIFTEN_PrioritySerializer_H + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/Priority.h" + +namespace Swift { + class PrioritySerializer : public GenericPayloadSerializer<Priority> { + public: + PrioritySerializer() : GenericPayloadSerializer<Priority>() {} + + virtual String serializePayload(boost::shared_ptr<Priority> priority) const { + return "<priority>" + boost::lexical_cast<std::string>(priority->getPriority()) + "</priority>"; + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp new file mode 100644 index 0000000..93ab136 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.cpp @@ -0,0 +1,28 @@ +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +namespace Swift { + +ResourceBindSerializer::ResourceBindSerializer() : GenericPayloadSerializer<ResourceBind>() { +} + +String ResourceBindSerializer::serializePayload(boost::shared_ptr<ResourceBind> resourceBind) const { + XMLElement bindElement("bind", "urn:ietf:params:xml:ns:xmpp-bind"); + if (resourceBind->getJID().isValid()) { + boost::shared_ptr<XMLElement> jidNode(new XMLElement("jid")); + jidNode->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(resourceBind->getJID().toString()))); + bindElement.addNode(jidNode); + } + else if (!resourceBind->getResource().isEmpty()) { + boost::shared_ptr<XMLElement> resourceNode(new XMLElement("resource")); + resourceNode->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(resourceBind->getResource()))); + bindElement.addNode(resourceNode); + } + return bindElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h new file mode 100644 index 0000000..61cf539 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_ResourceBindSerializer_H +#define SWIFTEN_ResourceBindSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/ResourceBind.h" + +namespace Swift { + class ResourceBindSerializer : public GenericPayloadSerializer<ResourceBind> { + public: + ResourceBindSerializer(); + + virtual String serializePayload(boost::shared_ptr<ResourceBind>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp new file mode 100644 index 0000000..6329f86 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.cpp @@ -0,0 +1,45 @@ +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +RosterSerializer::RosterSerializer() : GenericPayloadSerializer<RosterPayload>() { +} + +String RosterSerializer::serializePayload(boost::shared_ptr<RosterPayload> roster) const { + XMLElement queryElement("query", "jabber:iq:roster"); + foreach(const RosterItemPayload& item, roster->getItems()) { + boost::shared_ptr<XMLElement> itemElement(new XMLElement("item")); + itemElement->setAttribute("jid", item.getJID()); + itemElement->setAttribute("name", item.getName()); + + switch (item.getSubscription()) { + case RosterItemPayload::To: itemElement->setAttribute("subscription", "to"); break; + case RosterItemPayload::From: itemElement->setAttribute("subscription", "from"); break; + case RosterItemPayload::Both: itemElement->setAttribute("subscription", "both"); break; + case RosterItemPayload::Remove: itemElement->setAttribute("subscription", "remove"); break; + case RosterItemPayload::None: itemElement->setAttribute("subscription", "none"); break; + } + + if (item.getSubscriptionRequested()) { + itemElement->setAttribute("ask", "subscribe"); + } + + foreach(const String& group, item.getGroups()) { + boost::shared_ptr<XMLElement> groupElement(new XMLElement("group")); + groupElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(group))); + itemElement->addNode(groupElement); + } + + queryElement.addNode(itemElement); + } + + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/RosterSerializer.h b/Swiften/Serializer/PayloadSerializers/RosterSerializer.h new file mode 100644 index 0000000..fb6a713 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/RosterSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_RosterSerializer_H +#define SWIFTEN_RosterSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/RosterPayload.h" + +namespace Swift { + class RosterSerializer : public GenericPayloadSerializer<RosterPayload> { + public: + RosterSerializer(); + + virtual String serializePayload(boost::shared_ptr<RosterPayload>) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp new file mode 100644 index 0000000..e53c6b7 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp @@ -0,0 +1,38 @@ +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + +SecurityLabelSerializer::SecurityLabelSerializer() : GenericPayloadSerializer<SecurityLabel>() { +} + +String SecurityLabelSerializer::serializePayload(boost::shared_ptr<SecurityLabel> label) const { + XMLElement element("securitylabel", "urn:xmpp:sec-label:0"); + if (!label->getDisplayMarking().isEmpty()) { + boost::shared_ptr<XMLElement> displayMarking(new XMLElement("displaymarking")); + if (!label->getForegroundColor().isEmpty()) { + displayMarking->setAttribute("fgcolor", label->getForegroundColor()); + } + if (!label->getBackgroundColor().isEmpty()) { + displayMarking->setAttribute("bgcolor", label->getBackgroundColor()); + } + displayMarking->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(label->getDisplayMarking()))); + element.addNode(displayMarking); + } + if (!label->getLabel().isEmpty()) { + boost::shared_ptr<XMLElement> labelElement(new XMLElement("label")); + labelElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(label->getLabel()))); + element.addNode(labelElement); + } + foreach(const String& equivalentLabel, label->getEquivalentLabels()) { + boost::shared_ptr<XMLElement> equivalentLabelElement(new XMLElement("equivalentlabel")); + equivalentLabelElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(equivalentLabel))); + element.addNode(equivalentLabelElement); + } + return element.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h new file mode 100644 index 0000000..b0b0804 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SecurityLabelSerializer_H +#define SWIFTEN_SecurityLabelSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SecurityLabel.h" + +namespace Swift { + class SecurityLabelSerializer : public GenericPayloadSerializer<SecurityLabel> { + public: + SecurityLabelSerializer(); + + virtual String serializePayload(boost::shared_ptr<SecurityLabel> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp new file mode 100644 index 0000000..bc6a41f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp @@ -0,0 +1,30 @@ +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" + +namespace Swift { + +SecurityLabelsCatalogSerializer::SecurityLabelsCatalogSerializer() : GenericPayloadSerializer<SecurityLabelsCatalog>() { +} + +String SecurityLabelsCatalogSerializer::serializePayload(boost::shared_ptr<SecurityLabelsCatalog> catalog) const { + XMLElement element("catalog", "urn:xmpp:sec-label:catalog:0"); + if (!catalog->getName().isEmpty()) { + element.setAttribute("name", catalog->getName()); + } + if (catalog->getTo().isValid()) { + element.setAttribute("to", catalog->getTo()); + } + if (!catalog->getDescription().isEmpty()) { + element.setAttribute("desc", catalog->getDescription()); + } + foreach (const SecurityLabel& label, catalog->getLabels()) { + String serializedLabel = SecurityLabelSerializer().serialize(boost::shared_ptr<SecurityLabel>(new SecurityLabel(label))); + element.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(serializedLabel))); + } + return element.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h new file mode 100644 index 0000000..d086c79 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SecurityLabelsCatalogSerializer_H +#define SWIFTEN_SecurityLabelsCatalogSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SecurityLabelsCatalog.h" + +namespace Swift { + class SecurityLabelsCatalogSerializer : public GenericPayloadSerializer<SecurityLabelsCatalog> { + public: + SecurityLabelsCatalogSerializer(); + + virtual String serializePayload(boost::shared_ptr<SecurityLabelsCatalog> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp new file mode 100644 index 0000000..71ad9a2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp @@ -0,0 +1,23 @@ +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" + +namespace Swift { + +SoftwareVersionSerializer::SoftwareVersionSerializer() : GenericPayloadSerializer<SoftwareVersion>() { +} + +String SoftwareVersionSerializer::serializePayload(boost::shared_ptr<SoftwareVersion> version) const { + String result("<query xmlns=\"jabber:iq:version\">"); + if (!version->getName().isEmpty()) { + result += "<name>" + version->getName() + "</name>"; + } + if (!version->getVersion().isEmpty()) { + result += "<version>" + version->getVersion() + "</version>"; + } + if (!version->getOS().isEmpty()) { + result += "<os>" + version->getOS() + "</os>"; + } + result += "</query>"; + return result; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h new file mode 100644 index 0000000..50242f2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_SoftwareVersionSerializer_H +#define SWIFTEN_SoftwareVersionSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/SoftwareVersion.h" + +namespace Swift { + class SoftwareVersionSerializer : public GenericPayloadSerializer<SoftwareVersion> { + public: + SoftwareVersionSerializer(); + + virtual String serializePayload(boost::shared_ptr<SoftwareVersion> version) const; + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h b/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h new file mode 100644 index 0000000..df35054 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h @@ -0,0 +1,20 @@ +#ifndef SWIFTEN_StartSessionSerializer_H +#define SWIFTEN_StartSessionSerializer_H + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/StartSession.h" + +namespace Swift { + class StartSessionSerializer : public GenericPayloadSerializer<StartSession> { + public: + StartSessionSerializer() : GenericPayloadSerializer<StartSession>() {} + + virtual String serializePayload(boost::shared_ptr<StartSession>) const { + return XMLElement("session", "urn:ietf:params:xml:ns:xmpp-session").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StatusSerializer.h b/Swiften/Serializer/PayloadSerializers/StatusSerializer.h new file mode 100644 index 0000000..dc5f6d1 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StatusSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StatusSerializer_H +#define SWIFTEN_StatusSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Elements/Status.h" + +namespace Swift { + class StatusSerializer : public GenericPayloadSerializer<Status> { + public: + StatusSerializer() : GenericPayloadSerializer<Status>() {} + + virtual String serializePayload(boost::shared_ptr<Status> status) const { + XMLElement element("status"); + element.addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(status->getText()))); + return element.serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h b/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h new file mode 100644 index 0000000..f66c09c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h @@ -0,0 +1,33 @@ +#ifndef SWIFTEN_StatusShowSerializer_H +#define SWIFTEN_StatusShowSerializer_H + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/StatusShow.h" + +namespace Swift { + class StatusShowSerializer : public GenericPayloadSerializer<StatusShow> { + public: + StatusShowSerializer() : GenericPayloadSerializer<StatusShow>() {} + + virtual String serializePayload(boost::shared_ptr<StatusShow> statusShow) const { + if (statusShow->getType () == StatusShow::Online || statusShow->getType() == StatusShow::None) { + return ""; + } + else { + String result("<show>"); + switch (statusShow->getType()) { + case StatusShow::Away: result += "away"; break; + case StatusShow::XA: result += "xa"; break; + case StatusShow::FFC: result += "chat"; break; + case StatusShow::DND: result += "dnd"; break; + case StatusShow::Online: assert(false); break; + case StatusShow::None: assert(false); break; + } + result += "</show>"; + return result; + } + } + }; +} + +#endif diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp new file mode 100644 index 0000000..650a7ee --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h" + +using namespace Swift; + +class CapsInfoSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(CapsInfoSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + CapsInfoSerializerTest() {} + + void testSerialize() { + CapsInfoSerializer testling; + boost::shared_ptr<CapsInfo> priority(new CapsInfo("http://swift.im", "myversion", "sha-1")); + + CPPUNIT_ASSERT_EQUAL(String("<c hash=\"sha-1\" node=\"http://swift.im\" ver=\"myversion\" xmlns=\"http://jabber.org/protocol/caps\"/>"), testling.serialize(priority)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CapsInfoSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp new file mode 100644 index 0000000..d3e247f --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp @@ -0,0 +1,38 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" + +using namespace Swift; + +class DiscoInfoSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(DiscoInfoSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + DiscoInfoSerializerTest() {} + + void testSerialize() { + DiscoInfoSerializer testling; + boost::shared_ptr<DiscoInfo> discoInfo(new DiscoInfo()); + discoInfo->addIdentity(DiscoInfo::Identity("Swift", "client", "pc")); + discoInfo->addIdentity(DiscoInfo::Identity("Vlug", "client", "pc", "nl")); + discoInfo->addFeature("http://jabber.org/protocol/caps"); + discoInfo->addFeature("http://jabber.org/protocol/disco#info"); + discoInfo->setNode("http://swift.im#bla"); + + String expectedResult = + "<query node=\"http://swift.im#bla\" xmlns=\"http://jabber.org/protocol/disco#info\">" + "<identity category=\"client\" name=\"Swift\" type=\"pc\"/>" + "<identity category=\"client\" name=\"Vlug\" type=\"pc\" xml:lang=\"nl\"/>" + "<feature var=\"http://jabber.org/protocol/caps\"/>" + "<feature var=\"http://jabber.org/protocol/disco#info\"/>" + "</query>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(discoInfo)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DiscoInfoSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp new file mode 100644 index 0000000..2d68a3d --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h" + +using namespace Swift; + +class ErrorSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ErrorSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + ErrorSerializerTest() {} + + void testSerialize() { + ErrorSerializer testling; + boost::shared_ptr<Error> error(new Error(Error::BadRequest, Error::Cancel, "My Error")); + + CPPUNIT_ASSERT_EQUAL(String("<error type=\"cancel\"><bad-request xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/><text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">My Error</text></error>"), testling.serialize(error)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ErrorSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/Makefile.inc b/Swiften/Serializer/PayloadSerializers/UnitTest/Makefile.inc new file mode 100644 index 0000000..fce7ab9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/Makefile.inc @@ -0,0 +1,12 @@ +UNITTEST_SOURCES += \ + Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp \ + Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp new file mode 100644 index 0000000..5f6432b --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/PrioritySerializer.h" + +using namespace Swift; + +class PrioritySerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(PrioritySerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + PrioritySerializerTest() {} + + void testSerialize() { + PrioritySerializer testling; + boost::shared_ptr<Priority> priority(new Priority(-113)); + + CPPUNIT_ASSERT_EQUAL(String("<priority>-113</priority>"), testling.serialize(priority)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(PrioritySerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp new file mode 100644 index 0000000..ff09966 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp @@ -0,0 +1,49 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h" + +using namespace Swift; + +class ResourceBindSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(ResourceBindSerializerTest); + CPPUNIT_TEST(testSerialize_JID); + CPPUNIT_TEST(testSerialize_Resource); + CPPUNIT_TEST(testSerialize_Empty); + CPPUNIT_TEST_SUITE_END(); + + public: + ResourceBindSerializerTest() {} + + void testSerialize_JID() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + resourceBind->setJID(JID("somenode@example.com/someresource")); + + CPPUNIT_ASSERT_EQUAL(String( + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" + "<jid>somenode@example.com/someresource</jid>" + "</bind>"), testling.serialize(resourceBind)); + } + + void testSerialize_Resource() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + resourceBind->setResource("someresource"); + + CPPUNIT_ASSERT_EQUAL(String( + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" + "<resource>someresource</resource>" + "</bind>"), testling.serialize(resourceBind)); + } + + void testSerialize_Empty() { + ResourceBindSerializer testling; + boost::shared_ptr<ResourceBind> resourceBind(new ResourceBind()); + + CPPUNIT_ASSERT_EQUAL(String("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"), testling.serialize(resourceBind)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ResourceBindSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp new file mode 100644 index 0000000..81fdc09 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp @@ -0,0 +1,48 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h" + +using namespace Swift; + +class RosterSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RosterSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + RosterSerializerTest() {} + + void testSerialize() { + RosterSerializer testling; + boost::shared_ptr<RosterPayload> roster(new RosterPayload()); + + RosterItemPayload item1; + item1.setJID(JID("foo@bar.com")); + item1.setName("Foo @ Bar"); + item1.setSubscription(RosterItemPayload::From); + item1.addGroup("Group 1"); + item1.addGroup("Group 2"); + item1.setSubscriptionRequested(); + roster->addItem(item1); + + RosterItemPayload item2; + item2.setJID(JID("baz@blo.com")); + item2.setName("Baz"); + roster->addItem(item2); + + String expectedResult = + "<query xmlns=\"jabber:iq:roster\">" + "<item ask=\"subscribe\" jid=\"foo@bar.com\" name=\"Foo @ Bar\" subscription=\"from\">" + "<group>Group 1</group>" + "<group>Group 2</group>" + "</item>" + "<item jid=\"baz@blo.com\" name=\"Baz\" subscription=\"none\"/>" + "</query>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, testling.serialize(roster)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(RosterSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp new file mode 100644 index 0000000..e8b89ad --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelSerializerTest.cpp @@ -0,0 +1,43 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" + +using namespace Swift; + +class SecurityLabelSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelSerializerTest() {} + + void testSerialize() { + SecurityLabelSerializer testling; + boost::shared_ptr<SecurityLabel> securityLabel(new SecurityLabel()); + securityLabel->setDisplayMarking("SECRET"); + securityLabel->setForegroundColor("black"); + securityLabel->setBackgroundColor("red"); + securityLabel->setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"); + securityLabel->addEquivalentLabel("<icismlabel xmlns=\"http://example.gov/IC-ISM/0\" classification=\"S\" ownerProducer=\"USA\" disseminationControls=\"FOUO\"/>"); + securityLabel->addEquivalentLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MRUCAgD9DA9BcXVhIChvYnNvbGV0ZSk=</esssecuritylabel>"); + + CPPUNIT_ASSERT_EQUAL(String( + "<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>" + "<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>"), testling.serialize(securityLabel)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp new file mode 100644 index 0000000..acea1e4 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SecurityLabelsCatalogSerializerTest.cpp @@ -0,0 +1,52 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" + +using namespace Swift; + +class SecurityLabelsCatalogSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SecurityLabelsCatalogSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + SecurityLabelsCatalogSerializerTest() {} + + void testSerialize() { + SecurityLabelsCatalogSerializer testling; + boost::shared_ptr<SecurityLabelsCatalog> catalog(new SecurityLabelsCatalog()); + catalog->setTo(JID("example.com")); + catalog->setName("Default"); + catalog->setDescription("an example set of labels"); + + SecurityLabel securityLabel1; + securityLabel1.setDisplayMarking("SECRET"); + securityLabel1.setForegroundColor("black"); + securityLabel1.setBackgroundColor("red"); + securityLabel1.setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQYCAQQGASk=</esssecuritylabel>"); + catalog->addLabel(securityLabel1); + + SecurityLabel securityLabel2; + securityLabel2.setDisplayMarking("CONFIDENTIAL"); + securityLabel2.setForegroundColor("black"); + securityLabel2.setBackgroundColor("navy"); + securityLabel2.setLabel("<esssecuritylabel xmlns=\"urn:xmpp:sec-label:ess:0\">MQMGASk=</esssecuritylabel>"); + catalog->addLabel(securityLabel2); + + CPPUNIT_ASSERT_EQUAL(String( + "<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>"), testling.serialize(catalog)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SecurityLabelsCatalogSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp new file mode 100644 index 0000000..fd5dba5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h" + +using namespace Swift; + +class SoftwareVersionSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SoftwareVersionSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + SoftwareVersionSerializerTest() {} + + void testSerialize() { + SoftwareVersionSerializer testling; + boost::shared_ptr<SoftwareVersion> softwareVersion(new SoftwareVersion("Swift", "0.1", "Mac OS X")); + + CPPUNIT_ASSERT_EQUAL(String("<query xmlns=\"jabber:iq:version\"><name>Swift</name><version>0.1</version><os>Mac OS X</os></query>"), testling.serialize(softwareVersion)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SoftwareVersionSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp new file mode 100644 index 0000000..6dedacd --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp @@ -0,0 +1,25 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h" + +using namespace Swift; + +class StatusSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusSerializerTest() {} + + void testSerialize() { + StatusSerializer testling; + boost::shared_ptr<Status> status(new Status("I am away")); + + CPPUNIT_ASSERT_EQUAL(String("<status>I am away</status>"), testling.serialize(status)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp new file mode 100644 index 0000000..42e1c7c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp @@ -0,0 +1,58 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h" + +using namespace Swift; + +class StatusShowSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StatusShowSerializerTest); + CPPUNIT_TEST(testSerialize_Online); + CPPUNIT_TEST(testSerialize_Away); + CPPUNIT_TEST(testSerialize_FFC); + CPPUNIT_TEST(testSerialize_XA); + CPPUNIT_TEST(testSerialize_DND); + CPPUNIT_TEST_SUITE_END(); + + public: + StatusShowSerializerTest() {} + + void testSerialize_Online() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::Online)); + CPPUNIT_ASSERT_EQUAL(String(""), testling.serialize(statusShow)); + } + + void testSerialize_Away() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::Away)); + CPPUNIT_ASSERT_EQUAL(String("<show>away</show>"), testling.serialize(statusShow)); + } + + void testSerialize_FFC() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::FFC)); + CPPUNIT_ASSERT_EQUAL(String("<show>chat</show>"), testling.serialize(statusShow)); + } + + void testSerialize_XA() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::XA)); + CPPUNIT_ASSERT_EQUAL(String("<show>xa</show>"), testling.serialize(statusShow)); + } + + void testSerialize_DND() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::DND)); + CPPUNIT_ASSERT_EQUAL(String("<show>dnd</show>"), testling.serialize(statusShow)); + } + + void testSerialize_None() { + StatusShowSerializer testling; + boost::shared_ptr<StatusShow> statusShow(new StatusShow(StatusShow::None)); + CPPUNIT_ASSERT_EQUAL(String(""), testling.serialize(statusShow)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StatusShowSerializerTest); diff --git a/Swiften/Serializer/PresenceSerializer.cpp b/Swiften/Serializer/PresenceSerializer.cpp new file mode 100644 index 0000000..f7585d5 --- /dev/null +++ b/Swiften/Serializer/PresenceSerializer.cpp @@ -0,0 +1,27 @@ +#include "Swiften/Serializer/PresenceSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +#include "boost/shared_ptr.hpp" + +namespace Swift { + +PresenceSerializer::PresenceSerializer(PayloadSerializerCollection* payloadSerializers) : + GenericStanzaSerializer<Presence>("presence", payloadSerializers) { +} + +void PresenceSerializer::setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Presence> presence, + XMLElement& element) const { + switch (presence->getType()) { + case Presence::Unavailable: element.setAttribute("type","unavailable"); break; + case Presence::Probe: element.setAttribute("type","probe"); break; + case Presence::Subscribe: element.setAttribute("type","subscribe"); break; + case Presence::Subscribed: element.setAttribute("type","subscribed"); break; + case Presence::Unsubscribe: element.setAttribute("type","unsubscribe"); break; + case Presence::Unsubscribed: element.setAttribute("type","unsubscribed"); break; + case Presence::Error: element.setAttribute("type","error"); break; + case Presence::Available: break; + } +} + +} diff --git a/Swiften/Serializer/PresenceSerializer.h b/Swiften/Serializer/PresenceSerializer.h new file mode 100644 index 0000000..158d4f2 --- /dev/null +++ b/Swiften/Serializer/PresenceSerializer.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_PresenceSerializer_H +#define SWIFTEN_PresenceSerializer_H + +#include <cassert> + +#include "Swiften/Serializer/GenericStanzaSerializer.h" +#include "Swiften/Elements/Presence.h" + +namespace Swift { + class PresenceSerializer : public GenericStanzaSerializer<Presence> { + public: + PresenceSerializer(PayloadSerializerCollection* payloadSerializers); + + private: + virtual void setStanzaSpecificAttributesGeneric( + boost::shared_ptr<Presence> presence, + XMLElement& element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/StanzaSerializer.cpp b/Swiften/Serializer/StanzaSerializer.cpp new file mode 100644 index 0000000..d940634 --- /dev/null +++ b/Swiften/Serializer/StanzaSerializer.cpp @@ -0,0 +1,50 @@ +#include "Swiften/Serializer/StanzaSerializer.h" + +#include <sstream> +#include <typeinfo> +#include <iostream> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Elements/Stanza.h" + +namespace Swift { + +StanzaSerializer::StanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers) : tag_(tag), payloadSerializers_(payloadSerializers) { +} + +String StanzaSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<Stanza> stanza(boost::dynamic_pointer_cast<Stanza>(element)); + + XMLElement stanzaElement(tag_); + if (stanza->getFrom().isValid()) { + stanzaElement.setAttribute("from", stanza->getFrom()); + } + if (stanza->getTo().isValid()) { + stanzaElement.setAttribute("to", stanza->getTo()); + } + if (!stanza->getID().isEmpty()) { + stanzaElement.setAttribute("id", stanza->getID()); + } + setStanzaSpecificAttributes(stanza, stanzaElement); + + String serializedPayloads; + foreach (const boost::shared_ptr<Payload>& payload, stanza->getPayloads()) { + PayloadSerializer* serializer = payloadSerializers_->getPayloadSerializer(payload); + if (serializer) { + serializedPayloads += serializer->serialize(payload); + } + else { + std::cerr << "Could not find serializer for " << typeid(*(payload.get())).name() << std::endl; + } + } + if (!serializedPayloads.isEmpty()) { + stanzaElement.addNode(boost::shared_ptr<XMLNode>(new XMLRawTextNode(serializedPayloads))); + } + + return stanzaElement.serialize(); +} + +} diff --git a/Swiften/Serializer/StanzaSerializer.h b/Swiften/Serializer/StanzaSerializer.h new file mode 100644 index 0000000..0f7abaf --- /dev/null +++ b/Swiften/Serializer/StanzaSerializer.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_STANZASERIALIZER_H +#define SWIFTEN_STANZASERIALIZER_H + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Serializer/ElementSerializer.h" +#include "Swiften/Base/String.h" + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class StanzaSerializer : public ElementSerializer { + public: + StanzaSerializer(const String& tag, PayloadSerializerCollection* payloadSerializers); + + virtual String serialize(boost::shared_ptr<Element>) const; + virtual void setStanzaSpecificAttributes(boost::shared_ptr<Element>, XMLElement&) const = 0; + + private: + String tag_; + PayloadSerializerCollection* payloadSerializers_; + }; +} + +#endif diff --git a/Swiften/Serializer/StartTLSFailureSerializer.h b/Swiften/Serializer/StartTLSFailureSerializer.h new file mode 100644 index 0000000..472fea0 --- /dev/null +++ b/Swiften/Serializer/StartTLSFailureSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StartTLSFailureSerializer_H +#define SWIFTEN_StartTLSFailureSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StartTLSFailure.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class StartTLSFailureSerializer : public GenericElementSerializer<StartTLSFailure> { + public: + StartTLSFailureSerializer() : GenericElementSerializer<StartTLSFailure>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/StartTLSRequestSerializer.h b/Swiften/Serializer/StartTLSRequestSerializer.h new file mode 100644 index 0000000..fa85fe2 --- /dev/null +++ b/Swiften/Serializer/StartTLSRequestSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_StartTLSRequestSerializer_H +#define SWIFTEN_StartTLSRequestSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StartTLSRequest.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class StartTLSRequestSerializer : public GenericElementSerializer<StartTLSRequest> { + public: + StartTLSRequestSerializer() : GenericElementSerializer<StartTLSRequest>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/StreamFeaturesSerializer.cpp b/Swiften/Serializer/StreamFeaturesSerializer.cpp new file mode 100644 index 0000000..49e62c4 --- /dev/null +++ b/Swiften/Serializer/StreamFeaturesSerializer.cpp @@ -0,0 +1,46 @@ +#include "Swiften/Serializer/StreamFeaturesSerializer.h" + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + +StreamFeaturesSerializer::StreamFeaturesSerializer() { +} + +String StreamFeaturesSerializer::serialize(boost::shared_ptr<Element> element) const { + boost::shared_ptr<StreamFeatures> streamFeatures(boost::dynamic_pointer_cast<StreamFeatures>(element)); + + XMLElement streamFeaturesElement("stream:features"); + if (streamFeatures->hasStartTLS()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls"))); + } + if (!streamFeatures->getCompressionMethods().empty()) { + boost::shared_ptr<XMLElement> compressionElement(new XMLElement("compression", "http://jabber.org/features/compress")); + foreach(const String& method, streamFeatures->getCompressionMethods()) { + boost::shared_ptr<XMLElement> methodElement(new XMLElement("method")); + methodElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(method))); + compressionElement->addNode(methodElement); + } + streamFeaturesElement.addNode(compressionElement); + } + if (!streamFeatures->getAuthenticationMechanisms().empty()) { + boost::shared_ptr<XMLElement> mechanismsElement(new XMLElement("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl")); + foreach(const String& mechanism, streamFeatures->getAuthenticationMechanisms()) { + boost::shared_ptr<XMLElement> mechanismElement(new XMLElement("mechanism")); + mechanismElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(mechanism))); + mechanismsElement->addNode(mechanismElement); + } + streamFeaturesElement.addNode(mechanismsElement); + } + if (streamFeatures->hasResourceBind()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("bind", "urn:ietf:params:xml:ns:xmpp-bind"))); + } + if (streamFeatures->hasSession()) { + streamFeaturesElement.addNode(boost::shared_ptr<XMLElement>(new XMLElement("session", "urn:ietf:params:xml:ns:xmpp-session"))); + } + return streamFeaturesElement.serialize(); +} + +} diff --git a/Swiften/Serializer/StreamFeaturesSerializer.h b/Swiften/Serializer/StreamFeaturesSerializer.h new file mode 100644 index 0000000..f2da1bf --- /dev/null +++ b/Swiften/Serializer/StreamFeaturesSerializer.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_StreamFeaturesSerializer_H +#define SWIFTEN_StreamFeaturesSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/StreamFeatures.h" +#include "Swiften/Serializer/GenericElementSerializer.h" + +namespace Swift { + class StreamFeaturesSerializer : public GenericElementSerializer<StreamFeatures> { + public: + StreamFeaturesSerializer(); + + virtual String serialize(boost::shared_ptr<Element> element) const; + }; +} + +#endif diff --git a/Swiften/Serializer/TLSProceedSerializer.h b/Swiften/Serializer/TLSProceedSerializer.h new file mode 100644 index 0000000..d3829ac --- /dev/null +++ b/Swiften/Serializer/TLSProceedSerializer.h @@ -0,0 +1,22 @@ +#ifndef SWIFTEN_TLSProceedSerializer_H +#define SWIFTEN_TLSProceedSerializer_H + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Elements/TLSProceed.h" +#include "Swiften/Serializer/GenericElementSerializer.h" +#include "Swiften/Serializer/XML/XMLElement.h" + +namespace Swift { + class TLSProceedSerializer : public GenericElementSerializer<TLSProceed> { + public: + TLSProceedSerializer() : GenericElementSerializer<TLSProceed>() { + } + + virtual String serialize(boost::shared_ptr<Element>) const { + return XMLElement("proceed", "urn:ietf:params:xml:ns:xmpp-tls").serialize(); + } + }; +} + +#endif diff --git a/Swiften/Serializer/UnitTest/Makefile.inc b/Swiften/Serializer/UnitTest/Makefile.inc new file mode 100644 index 0000000..818663b --- /dev/null +++ b/Swiften/Serializer/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp diff --git a/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp new file mode 100644 index 0000000..187fe64 --- /dev/null +++ b/Swiften/Serializer/UnitTest/StreamFeaturesSerializerTest.cpp @@ -0,0 +1,46 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/StreamFeaturesSerializer.h" +#include "Swiften/Elements/StreamFeatures.h" + +using namespace Swift; + +class StreamFeaturesSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamFeaturesSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamFeaturesSerializerTest() {} + + void testSerialize() { + StreamFeaturesSerializer testling; + boost::shared_ptr<StreamFeatures> streamFeatures(new StreamFeatures()); + streamFeatures->setHasStartTLS(); + streamFeatures->addCompressionMethod("zlib"); + streamFeatures->addCompressionMethod("lzw"); + streamFeatures->addAuthenticationMechanism("DIGEST-MD5"); + streamFeatures->addAuthenticationMechanism("PLAIN"); + streamFeatures->setHasResourceBind(); + streamFeatures->setHasSession(); + + CPPUNIT_ASSERT_EQUAL(String( + "<stream:features>" + "<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>"), testling.serialize(streamFeatures)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamFeaturesSerializerTest); diff --git a/Swiften/Serializer/XML/Makefile.inc b/Swiften/Serializer/XML/Makefile.inc new file mode 100644 index 0000000..6daca82 --- /dev/null +++ b/Swiften/Serializer/XML/Makefile.inc @@ -0,0 +1,5 @@ +SWIFTEN_SOURCES += \ + Swiften/Serializer/XML/XMLNode.cpp \ + Swiften/Serializer/XML/XMLElement.cpp + +include Swiften/Serializer/XML/UnitTest/Makefile.inc diff --git a/Swiften/Serializer/XML/UnitTest/Makefile.inc b/Swiften/Serializer/XML/UnitTest/Makefile.inc new file mode 100644 index 0000000..fa00b76 --- /dev/null +++ b/Swiften/Serializer/XML/UnitTest/Makefile.inc @@ -0,0 +1,2 @@ +UNITTEST_SOURCES += \ + Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp diff --git a/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp b/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp new file mode 100644 index 0000000..49eb109 --- /dev/null +++ b/Swiften/Serializer/XML/UnitTest/XMLElementTest.cpp @@ -0,0 +1,62 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" + +using namespace Swift; + +class XMLElementTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMLElementTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST(testSerialize_NoChildren); + CPPUNIT_TEST(testSerialize_SpecialAttributeCharacters); + CPPUNIT_TEST(testSerialize_EmptyAttributeValue); + CPPUNIT_TEST_SUITE_END(); + + public: + XMLElementTest() {} + + void testSerialize() { + XMLElement testling("foo", "http://example.com"); + testling.setAttribute("myatt", "myval"); + boost::shared_ptr<XMLElement> barElement(new XMLElement("bar")); + barElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode("Blo"))); + testling.addNode(barElement); + boost::shared_ptr<XMLElement> bazElement(new XMLElement("baz")); + bazElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode("Bli"))); + testling.addNode(bazElement); + + String result = testling.serialize(); + String expectedResult = + "<foo myatt=\"myval\" xmlns=\"http://example.com\">" + "<bar>Blo</bar>" + "<baz>Bli</baz>" + "</foo>"; + + CPPUNIT_ASSERT_EQUAL(expectedResult, result); + } + + void testSerialize_NoChildren() { + XMLElement testling("foo", "http://example.com"); + + CPPUNIT_ASSERT_EQUAL(String("<foo xmlns=\"http://example.com\"/>"), testling.serialize()); + } + + void testSerialize_SpecialAttributeCharacters() { + XMLElement testling("foo"); + testling.setAttribute("myatt", "<\"'&>"); + + CPPUNIT_ASSERT_EQUAL(String("<foo myatt=\"<"'&>\"/>"), testling.serialize()); + } + + void testSerialize_EmptyAttributeValue() { + XMLElement testling("foo"); + testling.setAttribute("myatt", ""); + + CPPUNIT_ASSERT_EQUAL(String("<foo myatt=\"\"/>"), testling.serialize()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMLElementTest); diff --git a/Swiften/Serializer/XML/XMLElement.cpp b/Swiften/Serializer/XML/XMLElement.cpp new file mode 100644 index 0000000..af2cb34 --- /dev/null +++ b/Swiften/Serializer/XML/XMLElement.cpp @@ -0,0 +1,49 @@ +#include "Swiften/Serializer/XML/XMLElement.h" + +#include "Swiften/Base/foreach.h" + +namespace Swift { + +XMLElement::XMLElement(const String& tag, const String& xmlns) : + tag_(tag) { + if (!xmlns.isEmpty()) { + setAttribute("xmlns", xmlns); + } +} + +String XMLElement::serialize() { + String result; + result += "<" + tag_; + typedef std::pair<String,String> Pair; + foreach(const Pair& p, attributes_) { + result += " " + p.first + "=\"" + p.second + "\""; + } + + if (childNodes_.size() > 0) { + result += ">"; + foreach (boost::shared_ptr<XMLNode> node, childNodes_) { + result += node->serialize(); + } + result += "</" + tag_ + ">"; + } + else { + result += "/>"; + } + return result; +} + +void XMLElement::setAttribute(const String& attribute, const String& value) { + String escapedValue(value); + escapedValue.replaceAll('&', "&"); + escapedValue.replaceAll('<', "<"); + escapedValue.replaceAll('>', ">"); + escapedValue.replaceAll('\'', "'"); + escapedValue.replaceAll('"', """); + attributes_[attribute] = escapedValue; +} + +void XMLElement::addNode(boost::shared_ptr<XMLNode> node) { + childNodes_.push_back(node); +} + +} diff --git a/Swiften/Serializer/XML/XMLElement.h b/Swiften/Serializer/XML/XMLElement.h new file mode 100644 index 0000000..f2eb8bf --- /dev/null +++ b/Swiften/Serializer/XML/XMLElement.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_XMLElement_H +#define SWIFTEN_XMLElement_H + +#include <boost/shared_ptr.hpp> +#include <vector> +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLElement : public XMLNode { + public: + XMLElement(const String& tag, const String& xmlns = ""); + + void setAttribute(const String& attribute, const String& value); + void addNode(boost::shared_ptr<XMLNode> node); + + virtual String serialize(); + + private: + String tag_; + std::map<String, String> attributes_; + std::vector< boost::shared_ptr<XMLNode> > childNodes_; + }; +} +#endif diff --git a/Swiften/Serializer/XML/XMLNode.cpp b/Swiften/Serializer/XML/XMLNode.cpp new file mode 100644 index 0000000..1bef64a --- /dev/null +++ b/Swiften/Serializer/XML/XMLNode.cpp @@ -0,0 +1,8 @@ +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + +XMLNode::~XMLNode() { +} + +} diff --git a/Swiften/Serializer/XML/XMLNode.h b/Swiften/Serializer/XML/XMLNode.h new file mode 100644 index 0000000..b31c0d6 --- /dev/null +++ b/Swiften/Serializer/XML/XMLNode.h @@ -0,0 +1,15 @@ +#ifndef SWIFTEN_XMLNode_H +#define SWIFTEN_XMLNode_H + +#include "Swiften/Base/String.h" + +namespace Swift { + class XMLNode { + public: + virtual ~XMLNode(); + + virtual String serialize() = 0; + }; +} + +#endif diff --git a/Swiften/Serializer/XML/XMLRawTextNode.h b/Swiften/Serializer/XML/XMLRawTextNode.h new file mode 100644 index 0000000..e5800c3 --- /dev/null +++ b/Swiften/Serializer/XML/XMLRawTextNode.h @@ -0,0 +1,21 @@ +#ifndef SWIFTEN_XMLRawTextNode_H +#define SWIFTEN_XMLRawTextNode_H + +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLRawTextNode : public XMLNode { + public: + XMLRawTextNode(const String& text) : text_(text) { + } + + String serialize() { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Serializer/XML/XMLTextNode.h b/Swiften/Serializer/XML/XMLTextNode.h new file mode 100644 index 0000000..a142325 --- /dev/null +++ b/Swiften/Serializer/XML/XMLTextNode.h @@ -0,0 +1,23 @@ +#ifndef SWIFTEN_XMLTextNode_H +#define SWIFTEN_XMLTextNode_H + +#include "Swiften/Serializer/XML/XMLNode.h" + +namespace Swift { + class XMLTextNode : public XMLNode { + public: + XMLTextNode(const String& text) : text_(text) { + text_.replaceAll('&', "&"); // Should come first + text_.replaceAll('<', "<"); + } + + String serialize() { + return text_; + } + + private: + String text_; + }; +} + +#endif diff --git a/Swiften/Serializer/XMPPSerializer.cpp b/Swiften/Serializer/XMPPSerializer.cpp new file mode 100644 index 0000000..c43f9db --- /dev/null +++ b/Swiften/Serializer/XMPPSerializer.cpp @@ -0,0 +1,58 @@ +#include "Swiften/Serializer/XMPPSerializer.h" + +#include <boost/bind.hpp> +#include <iostream> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/CompressRequestSerializer.h" +#include "Swiften/Serializer/CompressFailureSerializer.h" +#include "Swiften/Serializer/StreamFeaturesSerializer.h" +#include "Swiften/Serializer/AuthRequestSerializer.h" +#include "Swiften/Serializer/AuthFailureSerializer.h" +#include "Swiften/Serializer/AuthSuccessSerializer.h" +#include "Swiften/Serializer/StartTLSRequestSerializer.h" +#include "Swiften/Serializer/StartTLSFailureSerializer.h" +#include "Swiften/Serializer/TLSProceedSerializer.h" +#include "Swiften/Serializer/MessageSerializer.h" +#include "Swiften/Serializer/PresenceSerializer.h" +#include "Swiften/Serializer/IQSerializer.h" + +namespace Swift { + +XMPPSerializer::XMPPSerializer(PayloadSerializerCollection* payloadSerializers) { + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new PresenceSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new IQSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new MessageSerializer(payloadSerializers))); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new CompressRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new CompressFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new AuthSuccessSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StartTLSRequestSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StartTLSFailureSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new TLSProceedSerializer())); + serializers_.push_back(boost::shared_ptr<ElementSerializer>(new StreamFeaturesSerializer())); +} + +String XMPPSerializer::serializeHeader(const String& domain) const { + return "<?xml version='1.0'?><stream:stream to='" + domain + "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' >"; +} + +String XMPPSerializer::serializeElement(boost::shared_ptr<Element> element) const { + std::vector< boost::shared_ptr<ElementSerializer> >::const_iterator i = std::find_if( + serializers_.begin(), serializers_.end(), + boost::bind(&ElementSerializer::canSerialize, _1, element)); + if (i != serializers_.end()) { + return (*i)->serialize(element); + } + else { + std::cerr << "Could not find serializer for " << typeid(*(element.get())).name() << std::endl; + return ""; + } +} + +String XMPPSerializer::serializeFooter() const { + return "</stream:stream>"; +} + +} diff --git a/Swiften/Serializer/XMPPSerializer.h b/Swiften/Serializer/XMPPSerializer.h new file mode 100644 index 0000000..1fc8b9d --- /dev/null +++ b/Swiften/Serializer/XMPPSerializer.h @@ -0,0 +1,28 @@ +#ifndef SWIFTEN_XMPPSERIALIZER_H +#define SWIFTEN_XMPPSERIALIZER_H + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include "Swiften/Elements/Element.h" +#include "Swiften/Base/String.h" +#include "Swiften/Serializer/ElementSerializer.h" + +namespace Swift { + class PayloadSerializerCollection; + class CompressRequestSerializer; + + class XMPPSerializer { + public: + XMPPSerializer(PayloadSerializerCollection*); + + String serializeHeader(const String& domain) const; + String serializeElement(boost::shared_ptr<Element> stanza) const; + String serializeFooter() const; + + private: + std::vector< boost::shared_ptr<ElementSerializer> > serializers_; + }; +} + +#endif diff --git a/Swiften/Settings/SettingsProvider.h b/Swiften/Settings/SettingsProvider.h new file mode 100644 index 0000000..ebf8d24 --- /dev/null +++ b/Swiften/Settings/SettingsProvider.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_SettingsProvider_H +#define SWIFTEN_SettingsProvider_H + +#include "Swiften/Base/String.h" + +namespace Swift { + +class SettingsProvider { + public: + virtual ~SettingsProvider() {} + virtual String getStringSetting(const String &settingPath) = 0; + virtual void storeString(const String &settingPath, const String &settingValue) = 0; +}; + +} +#endif + + diff --git a/Swiften/StreamStack/CompressionLayer.h b/Swiften/StreamStack/CompressionLayer.h new file mode 100644 index 0000000..6a8f2b3 --- /dev/null +++ b/Swiften/StreamStack/CompressionLayer.h @@ -0,0 +1,48 @@ +#ifndef SWIFTEN_COMPRESSIONLAYER_H +#define SWIFTEN_COMPRESSIONLAYER_H + +#include <boost/noncopyable.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/Compress/ZLibException.h" +#include "Swiften/Compress/ZLibCompressor.h" +#include "Swiften/Compress/ZLibDecompressor.h" + +namespace Swift { + class ZLibCompressor; + class ZLibDecompressor; + + class CompressionLayer : public StreamLayer, boost::noncopyable { + public: + CompressionLayer() {} + + virtual void writeData(const ByteArray& data) { + try { + onWriteData(compressor_.process(data)); + } + catch (const ZLibException& e) { + onError(); + } + } + + virtual void handleDataRead(const ByteArray& data) { + try { + onDataRead(decompressor_.process(data)); + } + catch (const ZLibException& e) { + onError(); + } + } + + public: + boost::signal<void ()> onError; + + private: + ZLibCompressor compressor_; + ZLibDecompressor decompressor_; + }; +} + +#endif diff --git a/Swiften/StreamStack/ConnectionLayer.h b/Swiften/StreamStack/ConnectionLayer.h new file mode 100644 index 0000000..99873a0 --- /dev/null +++ b/Swiften/StreamStack/ConnectionLayer.h @@ -0,0 +1,25 @@ +#ifndef SWIFTEN_CONNECTIONLAYER_H +#define SWIFTEN_CONNECTIONLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/Network/Connection.h" + +namespace Swift { + class ConnectionLayer : public LowLayer { + public: + ConnectionLayer(Connection* connection) : connection_(connection) { + connection_->onDataRead.connect(onDataRead); + } + + void writeData(const ByteArray& data) { + connection_->write(data); + } + + private: + Connection* connection_; + }; +} + +#endif diff --git a/Swiften/StreamStack/HighLayer.cpp b/Swiften/StreamStack/HighLayer.cpp new file mode 100644 index 0000000..2f5e1df --- /dev/null +++ b/Swiften/StreamStack/HighLayer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/HighLayer.h" + +namespace Swift { + +HighLayer::~HighLayer() { +} + +} diff --git a/Swiften/StreamStack/HighLayer.h b/Swiften/StreamStack/HighLayer.h new file mode 100644 index 0000000..bd6c6e6 --- /dev/null +++ b/Swiften/StreamStack/HighLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_HIGHLAYER_H +#define SWIFTEN_HIGHLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class HighLayer { + public: + virtual ~HighLayer(); + + virtual void handleDataRead(const ByteArray& data) = 0; + + boost::signal<void (const ByteArray&)> onWriteData; + }; +} + +#endif diff --git a/Swiften/StreamStack/LowLayer.cpp b/Swiften/StreamStack/LowLayer.cpp new file mode 100644 index 0000000..24aa7a2 --- /dev/null +++ b/Swiften/StreamStack/LowLayer.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/LowLayer.h" + +namespace Swift { + +LowLayer::~LowLayer() { +} + +} diff --git a/Swiften/StreamStack/LowLayer.h b/Swiften/StreamStack/LowLayer.h new file mode 100644 index 0000000..763cfc4 --- /dev/null +++ b/Swiften/StreamStack/LowLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_LOWLAYER_H +#define SWIFTEN_LOWLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class LowLayer { + public: + virtual ~LowLayer(); + + virtual void writeData(const ByteArray& data) = 0; + + boost::signal<void (const ByteArray&)> onDataRead; + }; +} + +#endif diff --git a/Swiften/StreamStack/Makefile.inc b/Swiften/StreamStack/Makefile.inc new file mode 100644 index 0000000..51f7b68 --- /dev/null +++ b/Swiften/StreamStack/Makefile.inc @@ -0,0 +1,15 @@ +SWIFTEN_SOURCES += \ + Swiften/StreamStack/XMPPLayer.cpp \ + Swiften/StreamStack/StreamStack.cpp \ + Swiften/StreamStack/LowLayer.cpp \ + Swiften/StreamStack/HighLayer.cpp \ + Swiften/StreamStack/WhitespacePingLayer.cpp \ + Swiften/StreamStack/TLSLayerFactory.cpp \ + Swiften/StreamStack/PlatformTLSLayerFactory.cpp + +ifeq ($(HAVE_OPENSSL),yes) +SWIFTEN_SOURCES += \ + Swiften/StreamStack/OpenSSLLayer.cpp +endif + +include Swiften/StreamStack/UnitTest/Makefile.inc diff --git a/Swiften/StreamStack/OpenSSLLayer.cpp b/Swiften/StreamStack/OpenSSLLayer.cpp new file mode 100644 index 0000000..da93e4e --- /dev/null +++ b/Swiften/StreamStack/OpenSSLLayer.cpp @@ -0,0 +1,28 @@ +#include "Swiften/StreamStack/OpenSSLLayer.h" + +namespace Swift { + +OpenSSLLayer::OpenSSLLayer() { + context_.onDataForNetwork.connect(onWriteData); + context_.onDataForApplication.connect(onDataRead); + context_.onConnected.connect(onConnected); + context_.onError.connect(onError); +} + +void OpenSSLLayer::connect() { + context_.connect(); +} + +void OpenSSLLayer::writeData(const ByteArray& data) { + context_.handleDataFromApplication(data); +} + +void OpenSSLLayer::handleDataRead(const ByteArray& data) { + context_.handleDataFromNetwork(data); +} + +bool OpenSSLLayer::setClientCertificate(const PKCS12Certificate& certificate) { + return context_.setClientCertificate(certificate); +} + +} diff --git a/Swiften/StreamStack/OpenSSLLayer.h b/Swiften/StreamStack/OpenSSLLayer.h new file mode 100644 index 0000000..615c75e --- /dev/null +++ b/Swiften/StreamStack/OpenSSLLayer.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_OpenSSLLayer_H +#define SWIFTEN_OpenSSLLayer_H + +#include <boost/noncopyable.hpp> +#include <boost/signal.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/TLSLayer.h" +#include "Swiften/TLS/OpenSSL/OpenSSLContext.h" + +namespace Swift { + class OpenSSLLayer : public TLSLayer, boost::noncopyable { + public: + OpenSSLLayer(); + + virtual void connect(); + virtual bool setClientCertificate(const PKCS12Certificate&); + + virtual void writeData(const ByteArray& data); + virtual void handleDataRead(const ByteArray& data); + + private: + OpenSSLContext context_; + }; +} + +#endif diff --git a/Swiften/StreamStack/PlatformTLSLayerFactory.cpp b/Swiften/StreamStack/PlatformTLSLayerFactory.cpp new file mode 100644 index 0000000..bb1abf1 --- /dev/null +++ b/Swiften/StreamStack/PlatformTLSLayerFactory.cpp @@ -0,0 +1,34 @@ +#include "Swiften/StreamStack/PlatformTLSLayerFactory.h" + +#include <cassert> + +#ifdef HAVE_SWIFTEN_CONFIG_H +#include "Swiften/config.h" +#endif +#ifdef HAVE_OPENSSL +#include "Swiften/StreamStack/OpenSSLLayer.h" +#endif + +namespace Swift { + +PlatformTLSLayerFactory::PlatformTLSLayerFactory() { +} + +bool PlatformTLSLayerFactory::canCreate() const { +#ifdef HAVE_OPENSSL + return true; +#else + return false; +#endif +} + +TLSLayer* PlatformTLSLayerFactory::createTLSLayer() { +#ifdef HAVE_OPENSSL + return new OpenSSLLayer(); +#else + assert(false); + return 0; +#endif +} + +} diff --git a/Swiften/StreamStack/PlatformTLSLayerFactory.h b/Swiften/StreamStack/PlatformTLSLayerFactory.h new file mode 100644 index 0000000..6ebee32 --- /dev/null +++ b/Swiften/StreamStack/PlatformTLSLayerFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_OpenSSLLayerFactory_H +#define SWIFTEN_OpenSSLLayerFactory_H + +#include "Swiften/StreamStack/TLSLayerFactory.h" + +namespace Swift { + class PlatformTLSLayerFactory : public TLSLayerFactory { + public: + PlatformTLSLayerFactory(); + + bool canCreate() const; + virtual TLSLayer* createTLSLayer(); + }; +} + +#endif diff --git a/Swiften/StreamStack/StreamLayer.h b/Swiften/StreamStack/StreamLayer.h new file mode 100644 index 0000000..6ea2051 --- /dev/null +++ b/Swiften/StreamStack/StreamLayer.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_STREAMLAYER_H +#define SWIFTEN_STREAMLAYER_H + +#include <boost/signal.hpp> + +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/HighLayer.h" + +namespace Swift { + class StreamLayer : public LowLayer, public HighLayer { + public: + StreamLayer() {} + }; +} + +#endif diff --git a/Swiften/StreamStack/StreamStack.cpp b/Swiften/StreamStack/StreamStack.cpp new file mode 100644 index 0000000..7dde858 --- /dev/null +++ b/Swiften/StreamStack/StreamStack.cpp @@ -0,0 +1,29 @@ +#include "Swiften/StreamStack/StreamStack.h" + +#include <boost/bind.hpp> + +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/StreamLayer.h" + +namespace Swift { + +StreamStack::StreamStack(XMPPLayer* xmppLayer, LowLayer* physicalLayer) : xmppLayer_(xmppLayer), physicalLayer_(physicalLayer) { + xmppReadSlotConnection_ = physicalLayer_->onDataRead.connect(boost::bind(&XMPPLayer::parseData, xmppLayer_, _1)); + xmppWriteSignalConnection_ = xmppLayer_->onWriteData.connect(boost::bind(&LowLayer::writeData, physicalLayer_, _1)); +} + +void StreamStack::addLayer(StreamLayer* newLayer) { + xmppReadSlotConnection_.disconnect(); + xmppWriteSignalConnection_.disconnect(); + + LowLayer* lowLayer = (layers_.empty() ? physicalLayer_ : *layers_.rbegin()); + + lowLayer->onDataRead.connect(boost::bind(&HighLayer::handleDataRead, newLayer, _1), boost::bsignals::at_front); + newLayer->onWriteData.connect(boost::bind(&LowLayer::writeData, lowLayer, _1), boost::bsignals::at_front); + xmppWriteSignalConnection_ = xmppLayer_->onWriteData.connect(boost::bind(&LowLayer::writeData, newLayer, _1), boost::bsignals::at_front); + xmppReadSlotConnection_ = newLayer->onDataRead.connect(boost::bind(&XMPPLayer::parseData, xmppLayer_, _1), boost::bsignals::at_front); + layers_.push_back(newLayer); +} + +} diff --git a/Swiften/StreamStack/StreamStack.h b/Swiften/StreamStack/StreamStack.h new file mode 100644 index 0000000..bc2e89b --- /dev/null +++ b/Swiften/StreamStack/StreamStack.h @@ -0,0 +1,45 @@ +#ifndef SWIFTEN_STREAMSTACK_H +#define SWIFTEN_STREAMSTACK_H + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <vector> + +#include "Swiften/Elements/Stanza.h" +#include "Swiften/Base/foreach.h" + +namespace Swift { + class XMPPLayer; + class LowLayer; + class StreamLayer; + + class StreamStack { + public: + StreamStack(XMPPLayer* xmppLayer, LowLayer* physicalLayer); + + void addLayer(StreamLayer*); + + XMPPLayer* getXMPPLayer() const { + return xmppLayer_; + } + + template<typename T> T* getLayer() { + foreach(StreamLayer* streamLayer, layers_) { + T* layer = dynamic_cast<T*>(streamLayer); + if (layer) { + return layer; + } + } + return 0; + } + + private: + XMPPLayer* xmppLayer_; + LowLayer* physicalLayer_; + std::vector<StreamLayer*> layers_; + boost::bsignals::connection xmppReadSlotConnection_; + boost::bsignals::connection xmppWriteSignalConnection_; + }; +} + +#endif diff --git a/Swiften/StreamStack/TLSLayer.h b/Swiften/StreamStack/TLSLayer.h new file mode 100644 index 0000000..490e16c --- /dev/null +++ b/Swiften/StreamStack/TLSLayer.h @@ -0,0 +1,19 @@ +#ifndef SWIFTEN_TLSLayer_H +#define SWIFTEN_TLSLayer_H + +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +namespace Swift { + class TLSLayer : public StreamLayer { + public: + virtual void connect() = 0; + virtual bool setClientCertificate(const PKCS12Certificate&) = 0; + + public: + boost::signal<void ()> onError; + boost::signal<void ()> onConnected; + }; +} + +#endif diff --git a/Swiften/StreamStack/TLSLayerFactory.cpp b/Swiften/StreamStack/TLSLayerFactory.cpp new file mode 100644 index 0000000..15de455 --- /dev/null +++ b/Swiften/StreamStack/TLSLayerFactory.cpp @@ -0,0 +1,8 @@ +#include "Swiften/StreamStack/TLSLayerFactory.h" + +namespace Swift { + +TLSLayerFactory::~TLSLayerFactory() { +} + +} diff --git a/Swiften/StreamStack/TLSLayerFactory.h b/Swiften/StreamStack/TLSLayerFactory.h new file mode 100644 index 0000000..b4218a5 --- /dev/null +++ b/Swiften/StreamStack/TLSLayerFactory.h @@ -0,0 +1,16 @@ +#ifndef SWIFTEN_TLSLayerFactory_H +#define SWIFTEN_TLSLayerFactory_H + +namespace Swift { + class TLSLayer; + + class TLSLayerFactory { + public: + virtual ~TLSLayerFactory(); + virtual bool canCreate() const = 0; + + virtual TLSLayer* createTLSLayer() = 0; + }; +} + +#endif diff --git a/Swiften/StreamStack/UnitTest/Makefile.inc b/Swiften/StreamStack/UnitTest/Makefile.inc new file mode 100644 index 0000000..dba5f5a --- /dev/null +++ b/Swiften/StreamStack/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp \ + Swiften/StreamStack/UnitTest/StreamStackTest.cpp diff --git a/Swiften/StreamStack/UnitTest/StreamStackTest.cpp b/Swiften/StreamStack/UnitTest/StreamStackTest.cpp new file mode 100644 index 0000000..1f6aad7 --- /dev/null +++ b/Swiften/StreamStack/UnitTest/StreamStackTest.cpp @@ -0,0 +1,167 @@ +#include <vector> +#include <boost/bind.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/StreamStack.h" +#include "Swiften/StreamStack/LowLayer.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/StreamStack/StreamLayer.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +using namespace Swift; + +class StreamStackTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(StreamStackTest); + CPPUNIT_TEST(testWriteData_NoIntermediateStreamStack); + CPPUNIT_TEST(testWriteData_OneIntermediateStream); + CPPUNIT_TEST(testWriteData_TwoIntermediateStreamStack); + CPPUNIT_TEST(testReadData_NoIntermediateStreamStack); + CPPUNIT_TEST(testReadData_OneIntermediateStream); + CPPUNIT_TEST(testReadData_TwoIntermediateStreamStack); + CPPUNIT_TEST(testAddLayer_ExistingOnWriteDataSlot); + CPPUNIT_TEST_SUITE_END(); + + public: + StreamStackTest() {} + + void setUp() { + physicalStream_ = new TestLowLayer(); + xmppStream_ = new XMPPLayer(&parserFactories_, &serializers_); + elementsReceived_ = 0; + dataWriteReceived_ = 0; + } + + void tearDown() { + delete physicalStream_; + delete xmppStream_; + } + + void testWriteData_NoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("foo"), physicalStream_->data_[0]); + } + + void testWriteData_OneIntermediateStream() { + StreamStack testling(xmppStream_, physicalStream_); + std::auto_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + testling.addLayer(xStream.get()); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("Xfoo"), physicalStream_->data_[0]); + } + + void testWriteData_TwoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + std::auto_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + std::auto_ptr<MyStreamLayer> yStream(new MyStreamLayer("Y")); + testling.addLayer(xStream.get()); + testling.addLayer(yStream.get()); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size()); + CPPUNIT_ASSERT_EQUAL(ByteArray("XYfoo"), physicalStream_->data_[0]); + } + + void testReadData_NoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + + physicalStream_->onDataRead(ByteArray("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testReadData_OneIntermediateStream() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + std::auto_ptr<MyStreamLayer> xStream(new MyStreamLayer("<")); + testling.addLayer(xStream.get()); + + physicalStream_->onDataRead(ByteArray("stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testReadData_TwoIntermediateStreamStack() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1)); + std::auto_ptr<MyStreamLayer> xStream(new MyStreamLayer("s")); + std::auto_ptr<MyStreamLayer> yStream(new MyStreamLayer("<")); + testling.addLayer(xStream.get()); + testling.addLayer(yStream.get()); + + physicalStream_->onDataRead(ByteArray("tream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>")); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + } + + void testAddLayer_ExistingOnWriteDataSlot() { + StreamStack testling(xmppStream_, physicalStream_); + xmppStream_->onWriteData.connect(boost::bind(&StreamStackTest::handleWriteData, this, _1)); + std::auto_ptr<MyStreamLayer> xStream(new MyStreamLayer("X")); + testling.addLayer(xStream.get()); + + xmppStream_->writeData("foo"); + + CPPUNIT_ASSERT_EQUAL(1, dataWriteReceived_); + } + + void handleElement(boost::shared_ptr<Element>) { + ++elementsReceived_; + } + + void handleWriteData(ByteArray) { + ++dataWriteReceived_; + } + + private: + class MyStreamLayer : public StreamLayer { + public: + MyStreamLayer(const String& prepend) : prepend_(prepend) { + } + + virtual void writeData(const ByteArray& data) { + onWriteData(ByteArray(prepend_) + data); + } + + virtual void handleDataRead(const ByteArray& data) { + onDataRead(ByteArray(prepend_) + data); + } + + private: + String prepend_; + }; + + class TestLowLayer : public LowLayer { + public: + TestLowLayer() { + } + + virtual void writeData(const ByteArray& data) { + data_.push_back(data); + } + + std::vector<ByteArray> data_; + }; + + private: + FullPayloadParserFactoryCollection parserFactories_; + FullPayloadSerializerCollection serializers_; + TestLowLayer* physicalStream_; + XMPPLayer* xmppStream_; + int elementsReceived_; + int dataWriteReceived_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamStackTest); diff --git a/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp b/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp new file mode 100644 index 0000000..2150f4d --- /dev/null +++ b/Swiften/StreamStack/UnitTest/XMPPLayerTest.cpp @@ -0,0 +1,116 @@ +#include <vector> +#include <boost/bind.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Elements/Presence.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" + +using namespace Swift; + +class XMPPLayerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(XMPPLayerTest); + CPPUNIT_TEST(testParseData_Error); + CPPUNIT_TEST(testResetParser); + CPPUNIT_TEST(testResetParser_FromSlot); + CPPUNIT_TEST(testWriteHeader); + CPPUNIT_TEST(testWriteElement); + CPPUNIT_TEST(testWriteFooter); + CPPUNIT_TEST_SUITE_END(); + + public: + XMPPLayerTest() {} + + void setUp() { + testling_ = new XMPPLayer(&parserFactories_, &serializers_); + elementsReceived_ = 0; + dataReceived_ = ""; + errorReceived_ = 0; + } + + void tearDown() { + delete testling_; + } + + void testParseData_Error() { + testling_->onError.connect(boost::bind(&XMPPLayerTest::handleError, this)); + + testling_->parseData("<iq>"); + + CPPUNIT_ASSERT_EQUAL(1, errorReceived_); + } + + void testResetParser() { + testling_->onElement.connect(boost::bind(&XMPPLayerTest::handleElement, this, _1)); + testling_->onError.connect(boost::bind(&XMPPLayerTest::handleError, this)); + + testling_->parseData("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' >"); + testling_->resetParser(); + testling_->parseData("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' >"); + testling_->parseData("<presence/>"); + + CPPUNIT_ASSERT_EQUAL(1, elementsReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorReceived_); + } + + void testResetParser_FromSlot() { + testling_->onElement.connect(boost::bind(&XMPPLayerTest::handleElementAndReset, this, _1)); + testling_->parseData("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' ><presence/>"); + testling_->parseData("<stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' ><presence/>"); + + CPPUNIT_ASSERT_EQUAL(2, elementsReceived_); + CPPUNIT_ASSERT_EQUAL(0, errorReceived_); + } + + void testWriteHeader() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + testling_->writeHeader("example.com"); + + CPPUNIT_ASSERT_EQUAL(String("<?xml version='1.0'?><stream:stream to='example.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' >"), dataReceived_); + } + + void testWriteElement() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + testling_->writeElement(boost::shared_ptr<Presence>(new Presence())); + + CPPUNIT_ASSERT_EQUAL(String("<presence/>"), dataReceived_); + } + + void testWriteFooter() { + testling_->onWriteData.connect(boost::bind(&XMPPLayerTest::handleWriteData, this, _1)); + testling_->writeFooter(); + + CPPUNIT_ASSERT_EQUAL(String("</stream:stream>"), dataReceived_); + } + + void handleElement(boost::shared_ptr<Element>) { + ++elementsReceived_; + } + + void handleElementAndReset(boost::shared_ptr<Element>) { + ++elementsReceived_; + testling_->resetParser(); + } + + void handleWriteData(ByteArray ba) { + dataReceived_ += std::string(ba.getData(), ba.getSize()); + } + + void handleError() { + ++errorReceived_; + } + + private: + FullPayloadParserFactoryCollection parserFactories_; + FullPayloadSerializerCollection serializers_; + XMPPLayer* testling_; + int elementsReceived_; + String dataReceived_; + int errorReceived_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPLayerTest); diff --git a/Swiften/StreamStack/WhitespacePingLayer.cpp b/Swiften/StreamStack/WhitespacePingLayer.cpp new file mode 100644 index 0000000..b9fbbd1 --- /dev/null +++ b/Swiften/StreamStack/WhitespacePingLayer.cpp @@ -0,0 +1,30 @@ +#include "Swiften/StreamStack/WhitespacePingLayer.h" +#include "Swiften/Network/Timer.h" + +namespace Swift { + +static const int TIMEOUT_MILLISECONDS = 60000; + +WhitespacePingLayer::WhitespacePingLayer() { + timer_ = new Timer(TIMEOUT_MILLISECONDS); + timer_->onTick.connect(boost::bind(&WhitespacePingLayer::handleTimerTick, this)); + timer_->start(); +} + +WhitespacePingLayer::~WhitespacePingLayer() { + delete timer_; +} + +void WhitespacePingLayer::writeData(const ByteArray& data) { + onWriteData(data); +} + +void WhitespacePingLayer::handleDataRead(const ByteArray& data) { + onDataRead(data); +} + +void WhitespacePingLayer::handleTimerTick() { + onWriteData(" "); +} + +} diff --git a/Swiften/StreamStack/WhitespacePingLayer.h b/Swiften/StreamStack/WhitespacePingLayer.h new file mode 100644 index 0000000..01fd3e3 --- /dev/null +++ b/Swiften/StreamStack/WhitespacePingLayer.h @@ -0,0 +1,27 @@ +#ifndef SWIFTEN_WhitespacePingLayer_H +#define SWIFTEN_WhitespacePingLayer_H + +#include <boost/noncopyable.hpp> + +#include "Swiften/StreamStack/StreamLayer.h" + +namespace Swift { + class Timer; + + class WhitespacePingLayer : public StreamLayer, boost::noncopyable { + public: + WhitespacePingLayer(); + ~WhitespacePingLayer(); + + void writeData(const ByteArray& data); + void handleDataRead(const ByteArray& data); + + private: + void handleTimerTick(); + + private: + Timer* timer_; + }; +} + +#endif diff --git a/Swiften/StreamStack/XMPPLayer.cpp b/Swiften/StreamStack/XMPPLayer.cpp new file mode 100644 index 0000000..7afa762 --- /dev/null +++ b/Swiften/StreamStack/XMPPLayer.cpp @@ -0,0 +1,79 @@ +#include "Swiften/StreamStack/XMPPLayer.h" +#include "Swiften/Parser/XMPPParser.h" +#include "Swiften/Serializer/XMPPSerializer.h" + +namespace Swift { + +XMPPLayer::XMPPLayer( + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers) : + payloadParserFactories_(payloadParserFactories), + payloadSerializers_(payloadSerializers), + resetParserAfterParse_(false), + inParser_(false) { + xmppParser_ = new XMPPParser(this, payloadParserFactories_); + xmppSerializer_ = new XMPPSerializer(payloadSerializers_); +} + +XMPPLayer::~XMPPLayer() { + delete xmppSerializer_; + delete xmppParser_; +} + +void XMPPLayer::writeHeader(const String& domain) { + onWriteData(ByteArray(xmppSerializer_->serializeHeader(domain))); +} + +void XMPPLayer::writeFooter() { + onWriteData(ByteArray(xmppSerializer_->serializeFooter())); +} + +void XMPPLayer::writeElement(boost::shared_ptr<Element> element) { + onWriteData(ByteArray(xmppSerializer_->serializeElement(element))); +} + +void XMPPLayer::writeData(const String& data) { + onWriteData(ByteArray(data)); +} + +void XMPPLayer::parseData(ByteArray data) { + onDataRead(data); + inParser_ = true; + if (!xmppParser_->parse(String(data.getData(), data.getSize()))) { + inParser_ = false; + onError(); + return; + } + inParser_ = false; + if (resetParserAfterParse_) { + doResetParser(); + } +} + +void XMPPLayer::doResetParser() { + delete xmppParser_; + xmppParser_ = new XMPPParser(this, payloadParserFactories_); + resetParserAfterParse_ = false; +} + +void XMPPLayer::handleStreamStart() { + onStreamStart(); +} + +void XMPPLayer::handleElement(boost::shared_ptr<Element> stanza) { + onElement(stanza); +} + +void XMPPLayer::handleStreamEnd() { +} + +void XMPPLayer::resetParser() { + if (inParser_) { + resetParserAfterParse_ = true; + } + else { + doResetParser(); + } +} + +} diff --git a/Swiften/StreamStack/XMPPLayer.h b/Swiften/StreamStack/XMPPLayer.h new file mode 100644 index 0000000..07b5b1c --- /dev/null +++ b/Swiften/StreamStack/XMPPLayer.h @@ -0,0 +1,57 @@ +#ifndef SWIFTEN_XMPPLAYER_H +#define SWIFTEN_XMPPLAYER_H + +#include <boost/shared_ptr.hpp> +#include <boost/signal.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Element.h" +#include "Swiften/Parser/XMPPParserClient.h" + +namespace Swift { + class XMPPParser; + class PayloadParserFactoryCollection; + class XMPPSerializer; + class PayloadSerializerCollection; + + class XMPPLayer : public XMPPParserClient, boost::noncopyable { + public: + XMPPLayer( + PayloadParserFactoryCollection* payloadParserFactories, + PayloadSerializerCollection* payloadSerializers); + ~XMPPLayer(); + + void writeHeader(const String& domain); + void writeFooter(); + void writeElement(boost::shared_ptr<Element>); + void writeData(const String& data); + + void parseData(ByteArray data); + void resetParser(); + + public: + boost::signal<void ()> onStreamStart; + boost::signal<void (boost::shared_ptr<Element>)> onElement; + boost::signal<void (const ByteArray&)> onWriteData; + boost::signal<void (const ByteArray&)> onDataRead; + boost::signal<void ()> onError; + + private: + void handleStreamStart(); + void handleElement(boost::shared_ptr<Element>); + void handleStreamEnd(); + + void doResetParser(); + + private: + PayloadParserFactoryCollection* payloadParserFactories_; + XMPPParser* xmppParser_; + PayloadSerializerCollection* payloadSerializers_; + XMPPSerializer* xmppSerializer_; + bool resetParserAfterParse_; + bool inParser_; + }; +} + +#endif diff --git a/Swiften/StringCodecs/Base64.cpp b/Swiften/StringCodecs/Base64.cpp new file mode 100644 index 0000000..03b0896 --- /dev/null +++ b/Swiften/StringCodecs/Base64.cpp @@ -0,0 +1,109 @@ +#include <boost/numeric/conversion/cast.hpp> + +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +String Base64::encode(const ByteArray &s) { + int i; + int len = s.getSize(); + char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + int a, b, c; + + std::string p; + p.resize((len+2)/3*4); + int at = 0; + for( i = 0; i < len; i += 3 ) { + a = ((unsigned char) (s[i]) & 3) << 4; + if(i + 1 < len) { + a += (unsigned char) (s[i + 1]) >> 4; + b = ((unsigned char) (s[i + 1]) & 0xF) << 2; + if(i + 2 < len) { + b += (unsigned char) (s[i + 2]) >> 6; + c = (unsigned char) (s[i + 2]) & 0x3F; + } + else + c = 64; + } + else { + b = c = 64; + } + + p[at++] = tbl[(unsigned char) (s[i]) >> 2]; + p[at++] = tbl[a]; + p[at++] = tbl[b]; + p[at++] = tbl[c]; + } + return p; +} + +ByteArray Base64::decode(const String& input) { + String inputWithoutNewlines(input); + inputWithoutNewlines.removeAll('\n'); + + const std::string& s = inputWithoutNewlines.getUTF8String(); + ByteArray p; + + // -1 specifies invalid + // 64 specifies eof + // everything else specifies data + + char tbl[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + // this should be a multiple of 4 + int len = s.size(); + + if(len % 4) { + return p; + } + + p.resize(len / 4 * 3); + + int i; + int at = 0; + + int a, b, c, d; + c = d = 0; + + for( i = 0; i < len; i += 4 ) { + a = tbl[boost::numeric_cast<int>(s[i])]; + b = tbl[boost::numeric_cast<int>(s[i + 1])]; + c = tbl[boost::numeric_cast<int>(s[i + 2])]; + d = tbl[boost::numeric_cast<int>(s[i + 3])]; + if((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) { + p.resize(0); + return p; + } + p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03); + p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F); + p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F); + } + + if(c & 64) + p.resize(at - 2); + else if(d & 64) + p.resize(at - 1); + + return p; +} + +} diff --git a/Swiften/StringCodecs/Base64.h b/Swiften/StringCodecs/Base64.h new file mode 100644 index 0000000..41ff62e --- /dev/null +++ b/Swiften/StringCodecs/Base64.h @@ -0,0 +1,18 @@ +#ifndef SWIFTEN_STRINGCODECS_BASE64_H +#define SWIFTEN_STRINGCODECS_BASE64_H + +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class Base64 + { + public: + static String encode(const ByteArray& s); + static ByteArray decode(const String &s); + }; +} + +#endif diff --git a/Swiften/StringCodecs/Makefile.inc b/Swiften/StringCodecs/Makefile.inc new file mode 100644 index 0000000..dd91014 --- /dev/null +++ b/Swiften/StringCodecs/Makefile.inc @@ -0,0 +1,5 @@ +SWIFTEN_SOURCES += \ + Swiften/StringCodecs/Base64.cpp \ + Swiften/StringCodecs/SHA1.cpp + +include Swiften/StringCodecs/UnitTest/Makefile.inc diff --git a/Swiften/StringCodecs/SHA1.cpp b/Swiften/StringCodecs/SHA1.cpp new file mode 100644 index 0000000..367f3c9 --- /dev/null +++ b/Swiften/StringCodecs/SHA1.cpp @@ -0,0 +1,196 @@ +#include "Swiften/Base/Platform.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include <boost/cstdint.hpp> +#include <stdio.h> +#include <string.h> + +typedef struct { + boost::uint32_t state[5]; + boost::uint32_t count[2]; + boost::uint8_t buffer[64]; +} SHA1_CTX; + +void SHA1Transform(boost::uint32_t state[5], boost::uint8_t buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, boost::uint8_t* data, unsigned int len); +void SHA1Final(boost::uint8_t digest[20], SHA1_CTX* context); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef SWIFTEN_LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(boost::uint32_t state[5], boost::uint8_t buffer[64]) +{ +boost::uint32_t a, b, c, d, e; +typedef union { + boost::uint8_t c[64]; + boost::uint32_t l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +#ifdef SHA1HANDSOFF +static boost::uint8_t workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = reinterpret_cast<CHAR64LONG16*>(buffer); +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, boost::uint8_t* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(boost::uint8_t digest[20], SHA1_CTX* context) +{ +boost::uint32_t i, j; +boost::uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (boost::uint8_t) ((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (boost::uint8_t *)("\200"), 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (boost::uint8_t *)("\0"), 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (boost::uint8_t) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + +// ----------------------------------------------------------------------------- + +#include "Swiften/StringCodecs/SHA1.h" + +namespace Swift { + +ByteArray SHA1::getBinaryHash(const ByteArray& input) { + ByteArray digest; + digest.resize(20); + SHA1_CTX context; + SHA1Init(&context); + SHA1Update(&context, (boost::uint8_t*) input.getData(), input.getSize()); + SHA1Final((boost::uint8_t*) digest.getData(), &context); + return digest; +} + +} diff --git a/Swiften/StringCodecs/SHA1.h b/Swiften/StringCodecs/SHA1.h new file mode 100644 index 0000000..7d432ac --- /dev/null +++ b/Swiften/StringCodecs/SHA1.h @@ -0,0 +1,13 @@ +#ifndef SWIFTEN_SHA1_H +#define SWIFTEN_SHA1_H + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class SHA1 { + public: + static ByteArray getBinaryHash(const ByteArray& data); + }; +} + +#endif diff --git a/Swiften/StringCodecs/UnitTest/Base64Test.cpp b/Swiften/StringCodecs/UnitTest/Base64Test.cpp new file mode 100644 index 0000000..22f3020 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/Base64Test.cpp @@ -0,0 +1,35 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/Base64.h" + +using namespace Swift; + +class Base64Test : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(Base64Test); + CPPUNIT_TEST(testEncode); + CPPUNIT_TEST(testEncode_NonAscii); + CPPUNIT_TEST(testDecode); + CPPUNIT_TEST_SUITE_END(); + + public: + Base64Test() {} + + void testEncode() { + String result(Base64::encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890")); + CPPUNIT_ASSERT_EQUAL(String("QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTA="), result); + } + + void testEncode_NonAscii() { + String result(Base64::encode(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"))); + CPPUNIT_ASSERT_EQUAL(String("QgayPKawpkPSDYmwT/WM94uAlu0="), result); + } + + void testDecode() { + ByteArray result(Base64::decode("QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Njc4OTA=")); + CPPUNIT_ASSERT_EQUAL(ByteArray("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(Base64Test); diff --git a/Swiften/StringCodecs/UnitTest/Makefile.inc b/Swiften/StringCodecs/UnitTest/Makefile.inc new file mode 100644 index 0000000..ce9fd60 --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/Makefile.inc @@ -0,0 +1,3 @@ +UNITTEST_SOURCES += \ + Swiften/StringCodecs/UnitTest/Base64Test.cpp \ + Swiften/StringCodecs/UnitTest/SHA1Test.cpp diff --git a/Swiften/StringCodecs/UnitTest/SHA1Test.cpp b/Swiften/StringCodecs/UnitTest/SHA1Test.cpp new file mode 100644 index 0000000..16e96ad --- /dev/null +++ b/Swiften/StringCodecs/UnitTest/SHA1Test.cpp @@ -0,0 +1,23 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/StringCodecs/SHA1.h" + +using namespace Swift; + +class SHA1Test : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(SHA1Test); + CPPUNIT_TEST(testEncode); + CPPUNIT_TEST_SUITE_END(); + + public: + SHA1Test() {} + + void testEncode() { + ByteArray result(SHA1::getBinaryHash("client/pc//Exodus 0.9.1<http://jabber.org/protocol/caps<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<")); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x42\x06\xb2\x3c\xa6\xb0\xa6\x43\xd2\x0d\x89\xb0\x4f\xf5\x8c\xf7\x8b\x80\x96\xed"), result); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SHA1Test); diff --git a/Swiften/Swift.pri b/Swiften/Swift.pri new file mode 100644 index 0000000..2ee62ef --- /dev/null +++ b/Swiften/Swift.pri @@ -0,0 +1,322 @@ +INCLUDEPATH += $$PWD/.. +!DUMMY { +} else { +} +DUMMY { +DUMMY { +!DUMMY { +} else { +} +} +} +## Begin File: 3rdParty/Boost/Makefile.inc +DEFINES += BOOST_ALL_NO_LIB +INCLUDEPATH += $$PWD/3rdParty/Boost +DEFINES += BOOST_SIGNALS_NAMESPACE=bsignals +SOURCES += $$PWD/3rdParty/Boost/libs/date_time/src/gregorian/date_generators.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/date_time/src/gregorian/greg_month.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/date_time/src/gregorian/greg_weekday.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/date_time/src/gregorian/gregorian_types.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/date_time/src/posix_time/posix_time_types.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/system/src/error_code.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/tss_null.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/signals/src/connection.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/signals/src/named_slot_map.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/signals/src/signal_base.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/signals/src/slot.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/signals/src/trackable.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/filesystem/src/operations.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/filesystem/src/path.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/filesystem/src/portability.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/filesystem/src/utf8_codecvt_facet.cpp +win32 { +DEFINES += _WIN32_WINNT=0x0501 +DEFINES += __USE_W32_SOCKETS +SOURCES += $$PWD/3rdParty/Boost/win32_stubs.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/win32/exceptions.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/win32/thread.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/win32/tss_dll.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/win32/tss_pe.cpp +} else { +LIBS += -lpthread +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/pthread/exceptions.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/pthread/once.cpp +SOURCES += $$PWD/3rdParty/Boost/libs/thread/src/pthread/thread.cpp +} +## End file +## Begin File: 3rdParty/CppUnit/Makefile.inc +INCLUDEPATH += $$PWD/3rdParty/CppUnit +## End file +## Begin File: 3rdParty/LibIDN/Makefile.inc +INCLUDEPATH += $$PWD/3rdParty/LibIDN/src +INCLUDEPATH += $$PWD/3rdParty/LibIDN/src +INCLUDEPATH += $$PWD/3rdParty/LibIDN/stubs +win32 { +DEFINES += IDNA_STATIC +INCLUDEPATH += $$PWD/3rdParty/LibIDN/stubs/win32 +DEFINES += strcasecmp=stricmp +DEFINES += strncasecmp=strnicmp +INCLUDEPATH += $$PWD/3rdParty/LibIDN/stubs/win32 +} +SOURCES += $$PWD/3rdParty/LibIDN/src/stringprep.c +SOURCES += $$PWD/3rdParty/LibIDN/src/profiles.c +SOURCES += $$PWD/3rdParty/LibIDN/src/rfc3454.c +SOURCES += $$PWD/3rdParty/LibIDN/src/punycode.c +SOURCES += $$PWD/3rdParty/LibIDN/src/idna.c +SOURCES += $$PWD/3rdParty/LibIDN/src/toutf8.c +SOURCES += $$PWD/3rdParty/LibIDN/src/nfkc.c +## End file +## Begin File: 3rdParty/ZLib/Makefile.inc +INCLUDEPATH += $$PWD/3rdParty/ZLib/src +INCLUDEPATH += $$PWD/3rdParty/ZLib/src +SOURCES += $$PWD/3rdParty/ZLib/src/adler32.c +SOURCES += $$PWD/3rdParty/ZLib/src/compress.c +SOURCES += $$PWD/3rdParty/ZLib/src/crc32.c +SOURCES += $$PWD/3rdParty/ZLib/src/deflate.c +SOURCES += $$PWD/3rdParty/ZLib/src/gzio.c +SOURCES += $$PWD/3rdParty/ZLib/src/infback.c +SOURCES += $$PWD/3rdParty/ZLib/src/inffast.c +SOURCES += $$PWD/3rdParty/ZLib/src/inflate.c +SOURCES += $$PWD/3rdParty/ZLib/src/inftrees.c +SOURCES += $$PWD/3rdParty/ZLib/src/trees.c +SOURCES += $$PWD/3rdParty/ZLib/src/uncompr.c +SOURCES += $$PWD/3rdParty/ZLib/src/zutil.c +## End file +## Begin File: Base/Makefile.inc +SOURCES += $$PWD/Base/String.cpp +SOURCES += $$PWD/Base/ByteArray.cpp +SOURCES += $$PWD/Base/IDGenerator.cpp +SOURCES += $$PWD/Base/sleep.cpp +## Begin File: Base/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Application/Makefile.inc +SOURCES += $$PWD/Application/Application.cpp +SOURCES += $$PWD/Application/ApplicationMessageDisplay.cpp +mac { +## Begin File: Application/MacOSX/Makefile.inc +SOURCES += $$PWD/Application/MacOSX/MacOSXApplication.cpp +## End file +} +## Begin File: Application/UnitTest/Makefile.inc +## End file +## End file +## Begin File: EventLoop/Makefile.inc +SOURCES += $$PWD/EventLoop/EventLoop.cpp +SOURCES += $$PWD/EventLoop/SimpleEventLoop.cpp +SOURCES += $$PWD/EventLoop/MainEventLoop.cpp +## Begin File: EventLoop/UnitTest/Makefile.inc +## End file +## End file +## Begin File: StringCodecs/Makefile.inc +SOURCES += $$PWD/StringCodecs/Base64.cpp +SOURCES += $$PWD/StringCodecs/SHA1.cpp +## Begin File: StringCodecs/UnitTest/Makefile.inc +## End file +## End file +## Begin File: JID/Makefile.inc +SOURCES += $$PWD/JID/JID.cpp +## Begin File: JID/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Elements/Makefile.inc +SOURCES += $$PWD/Elements/RosterPayload.cpp +SOURCES += $$PWD/Elements/Payload.cpp +SOURCES += $$PWD/Elements/Stanza.cpp +SOURCES += $$PWD/Elements/Element.cpp +SOURCES += $$PWD/Elements/DiscoInfo.cpp +SOURCES += $$PWD/Elements/IQ.cpp +## Begin File: Elements/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Events/Makefile.inc +## End file +## Begin File: StreamStack/Makefile.inc +SOURCES += $$PWD/StreamStack/XMPPLayer.cpp +SOURCES += $$PWD/StreamStack/StreamStack.cpp +SOURCES += $$PWD/StreamStack/LowLayer.cpp +SOURCES += $$PWD/StreamStack/HighLayer.cpp +SOURCES += $$PWD/StreamStack/WhitespacePingLayer.cpp +SOURCES += $$PWD/StreamStack/TLSLayerFactory.cpp +SOURCES += $$PWD/StreamStack/PlatformTLSLayerFactory.cpp +!isEmpty(HAVE_OPENSSL) { +SOURCES += $$PWD/StreamStack/OpenSSLLayer.cpp +} +## Begin File: StreamStack/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Serializer/Makefile.inc +SOURCES += $$PWD/Serializer/ElementSerializer.cpp +SOURCES += $$PWD/Serializer/CompressRequestSerializer.cpp +SOURCES += $$PWD/Serializer/AuthRequestSerializer.cpp +SOURCES += $$PWD/Serializer/StreamFeaturesSerializer.cpp +SOURCES += $$PWD/Serializer/XMPPSerializer.cpp +SOURCES += $$PWD/Serializer/StanzaSerializer.cpp +SOURCES += $$PWD/Serializer/PresenceSerializer.cpp +SOURCES += $$PWD/Serializer/MessageSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializerCollection.cpp +## Begin File: Serializer/UnitTest/Makefile.inc +## End file +## Begin File: Serializer/XML/Makefile.inc +SOURCES += $$PWD/Serializer/XML/XMLNode.cpp +SOURCES += $$PWD/Serializer/XML/XMLElement.cpp +## Begin File: Serializer/XML/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Serializer/PayloadSerializers/Makefile.inc +SOURCES += $$PWD/Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/ErrorSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/RosterSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/DiscoInfoSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/CapsInfoSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/ResourceBindSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/SecurityLabelSerializer.cpp +SOURCES += $$PWD/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp +## Begin File: Serializer/PayloadSerializers/UnitTest/Makefile.inc +## End file +## End file +## End file +## Begin File: Parser/Makefile.inc +SOURCES += $$PWD/Parser/XMLParser.cpp +SOURCES += $$PWD/Parser/XMLParserClient.cpp +SOURCES += $$PWD/Parser/XMLParserFactory.cpp +SOURCES += $$PWD/Parser/PlatformXMLParserFactory.cpp +SOURCES += $$PWD/Parser/XMPPParser.cpp +SOURCES += $$PWD/Parser/XMPPParserClient.cpp +SOURCES += $$PWD/Parser/MessageParser.cpp +SOURCES += $$PWD/Parser/IQParser.cpp +SOURCES += $$PWD/Parser/PresenceParser.cpp +SOURCES += $$PWD/Parser/StreamFeaturesParser.cpp +SOURCES += $$PWD/Parser/CompressParser.cpp +SOURCES += $$PWD/Parser/AuthRequestParser.cpp +SOURCES += $$PWD/Parser/StanzaParser.cpp +SOURCES += $$PWD/Parser/ElementParser.cpp +SOURCES += $$PWD/Parser/PayloadParser.cpp +SOURCES += $$PWD/Parser/PayloadParserFactory.cpp +SOURCES += $$PWD/Parser/PayloadParserFactoryCollection.cpp +SOURCES += $$PWD/Parser/SerializingParser.cpp +!isEmpty(HAVE_LIBXML) { +SOURCES += $$PWD/Parser/LibXMLParser.cpp +} +!isEmpty(HAVE_EXPAT) { +SOURCES += $$PWD/Parser/ExpatParser.cpp +} +## Begin File: Parser/PayloadParsers/Makefile.inc +SOURCES += $$PWD/Parser/PayloadParsers/BodyParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/PriorityParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/StatusParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/StatusShowParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/SoftwareVersionParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/SecurityLabelParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/SecurityLabelsCatalogParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/DiscoInfoParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/ErrorParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/RosterParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/ResourceBindParser.cpp +SOURCES += $$PWD/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +## Begin File: Parser/PayloadParsers/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Parser/UnitTest/Makefile.inc +## End file +## End file +## Begin File: MUC/Makefile.inc +SOURCES += $$PWD/MUC/MUC.cpp +SOURCES += $$PWD/MUC/MUCOccupant.cpp +## End file +## Begin File: Network/Makefile.inc +SOURCES += $$PWD/Network/HostAddress.cpp +SOURCES += $$PWD/Network/DomainNameResolver.cpp +SOURCES += $$PWD/Network/ConnectionFactory.cpp +SOURCES += $$PWD/Network/BoostConnection.cpp +SOURCES += $$PWD/Network/BoostConnectionFactory.cpp +SOURCES += $$PWD/Network/Timer.cpp +## Begin File: Network/UnitTest/Makefile.inc +## End file +!win32 { +LIBS += -lresolv +} +## End file +## Begin File: Client/Makefile.inc +SOURCES += $$PWD/Client/Client.cpp +SOURCES += $$PWD/Client/Session.cpp +## Begin File: Client/UnitTest/Makefile.inc +## End file +## End file +## Begin File: TLS/Makefile.inc +!isEmpty(HAVE_OPENSSL) { +## Begin File: TLS/OpenSSL/Makefile.inc +SOURCES += $$PWD/TLS/OpenSSL/OpenSSLContext.cpp +## End file +} +## End file +## Begin File: SASL/Makefile.inc +SOURCES += $$PWD/SASL/PLAINMessage.cpp +## Begin File: SASL/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Compress/Makefile.inc +SOURCES += $$PWD/Compress/ZLibCodecompressor.cpp +## Begin File: Compress/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Queries/Makefile.inc +SOURCES += $$PWD/Queries/IQRouter.cpp +SOURCES += $$PWD/Queries/IQHandler.cpp +SOURCES += $$PWD/Queries/IQChannel.cpp +SOURCES += $$PWD/Queries/Request.cpp +## Begin File: Queries/Responders/Makefile.inc +SOURCES += $$PWD/Queries/Responders/SoftwareVersionResponder.cpp +SOURCES += $$PWD/Queries/Responders/DiscoInfoResponder.cpp +## Begin File: Queries/Responders/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Queries/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Controllers/Makefile.inc +SOURCES += $$PWD/Controllers/ChatController.cpp +SOURCES += $$PWD/Controllers/ChatControllerBase.cpp +SOURCES += $$PWD/Controllers/MainController.cpp +SOURCES += $$PWD/Controllers/NickResolver.cpp +SOURCES += $$PWD/Controllers/RosterController.cpp +SOURCES += $$PWD/Controllers/XMPPRosterController.cpp +SOURCES += $$PWD/Controllers/MUCController.cpp +SOURCES += $$PWD/Controllers/EventController.cpp +## Begin File: Controllers/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Roster/Makefile.inc +SOURCES += $$PWD/Roster/ContactRosterItem.cpp +SOURCES += $$PWD/Roster/Roster.cpp +SOURCES += $$PWD/Roster/XMPPRoster.cpp +## Begin File: Roster/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Disco/Makefile.inc +SOURCES += $$PWD/Disco/CapsInfoGenerator.cpp +## Begin File: Disco/UnitTest/Makefile.inc +## End file +## End file +## Begin File: Examples/Makefile.inc +## Begin File: Examples/TuneBot/Makefile.inc +## End file +## End file +## Begin File: QA/Makefile.inc +DUMMY { +} +## Begin File: QA/UnitTest/Makefile.inc +## End file +## Begin File: QA/NetworkTest/Makefile.inc +## End file +## Begin File: QA/ClientTest/Makefile.inc +## End file +## End file +DUMMY { +DUMMY { +} +} +## End file diff --git a/Swiften/TLS/Makefile.inc b/Swiften/TLS/Makefile.inc new file mode 100644 index 0000000..6318a02 --- /dev/null +++ b/Swiften/TLS/Makefile.inc @@ -0,0 +1,3 @@ +ifeq ($(HAVE_OPENSSL),yes) +include Swiften/TLS/OpenSSL/Makefile.inc +endif diff --git a/Swiften/TLS/OpenSSL/Makefile.inc b/Swiften/TLS/OpenSSL/Makefile.inc new file mode 100644 index 0000000..5bda771 --- /dev/null +++ b/Swiften/TLS/OpenSSL/Makefile.inc @@ -0,0 +1,2 @@ +SWIFTEN_SOURCES += \ + Swiften/TLS/OpenSSL/OpenSSLContext.cpp diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp new file mode 100644 index 0000000..3ce8d54 --- /dev/null +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp @@ -0,0 +1,159 @@ +#include <vector> +#include <openssl/err.h> +#include <openssl/pkcs12.h> + +#include "Swiften/TLS/OpenSSL/OpenSSLContext.h" +#include "Swiften/TLS/PKCS12Certificate.h" + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace Swift { + +static const int SSL_READ_BUFFERSIZE = 8192; + +void freeX509Stack(STACK_OF(X509)* stack) { + sk_X509_free(stack); +} + +OpenSSLContext::OpenSSLContext() : state_(Start), context_(0), handle_(0), readBIO_(0), writeBIO_(0) { + ensureLibraryInitialized(); + context_ = SSL_CTX_new(TLSv1_client_method()); +} + +OpenSSLContext::~OpenSSLContext() { + SSL_free(handle_); + SSL_CTX_free(context_); +} + +void OpenSSLContext::ensureLibraryInitialized() { + static bool isLibraryInitialized = false; + if (!isLibraryInitialized) { + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + isLibraryInitialized = true; + } +} + +void OpenSSLContext::connect() { + handle_ = SSL_new(context_); + // Ownership of BIOs is ransferred + readBIO_ = BIO_new(BIO_s_mem()); + writeBIO_ = BIO_new(BIO_s_mem()); + SSL_set_bio(handle_, readBIO_, writeBIO_); + + state_ = Connecting; + doConnect(); +} + +void OpenSSLContext::doConnect() { + int connectResult = SSL_connect(handle_); + int error = SSL_get_error(handle_, connectResult); + switch (error) { + case SSL_ERROR_NONE: + state_ = Connected; + onConnected(); + //X509* x = SSL_get_peer_certificate(handle_); + //std::cout << x->name << std::endl; + //const char* comp = SSL_get_current_compression(handle_); + //std::cout << "Compression: " << SSL_COMP_get_name(comp) << std::endl; + break; + case SSL_ERROR_WANT_READ: + sendPendingDataToNetwork(); + break; + default: + state_ = Error; + onError(); + } +} + +void OpenSSLContext::sendPendingDataToNetwork() { + int size = BIO_pending(writeBIO_); + if (size > 0) { + ByteArray data; + data.resize(size); + BIO_read(writeBIO_, data.getData(), size); + onDataForNetwork(data); + } +} + +void OpenSSLContext::handleDataFromNetwork(const ByteArray& data) { + BIO_write(readBIO_, data.getData(), data.getSize()); + switch (state_) { + case Connecting: + doConnect(); + break; + case Connected: + sendPendingDataToApplication(); + break; + case Start: assert(false); break; + case Error: assert(false); break; + } +} + +void OpenSSLContext::handleDataFromApplication(const ByteArray& data) { + if (SSL_write(handle_, data.getData(), data.getSize()) >= 0) { + sendPendingDataToNetwork(); + } + else { + state_ = Error; + onError(); + } +} + +void OpenSSLContext::sendPendingDataToApplication() { + ByteArray data; + data.resize(SSL_READ_BUFFERSIZE); + int ret = SSL_read(handle_, data.getData(), data.getSize()); + while (ret > 0) { + data.resize(ret); + onDataForApplication(data); + data.resize(SSL_READ_BUFFERSIZE); + ret = SSL_read(handle_, data.getData(), data.getSize()); + } + if (ret < 0 && SSL_get_error(handle_, ret) != SSL_ERROR_WANT_READ) { + state_ = Error; + onError(); + } +} + +bool OpenSSLContext::setClientCertificate(const PKCS12Certificate& certificate) { + if (certificate.isNull()) { + return false; + } + + // Create a PKCS12 structure + BIO* bio = BIO_new(BIO_s_mem()); + BIO_write(bio, certificate.getData().getData(), certificate.getData().getSize()); + boost::shared_ptr<PKCS12> pkcs12(d2i_PKCS12_bio(bio, NULL), PKCS12_free); + BIO_free(bio); + if (!pkcs12) { + return false; + } + + // Parse PKCS12 + X509 *certPtr = 0; + EVP_PKEY* privateKeyPtr = 0; + STACK_OF(X509)* caCertsPtr = 0; + int result = PKCS12_parse(pkcs12.get(), certificate.getPassword().getUTF8Data(), &privateKeyPtr, &certPtr, &caCertsPtr); + if (result != 1) { + return false; + } + boost::shared_ptr<X509> cert(certPtr, X509_free); + boost::shared_ptr<EVP_PKEY> privateKey(privateKeyPtr, EVP_PKEY_free); + boost::shared_ptr<STACK_OF(X509)> caCerts(caCertsPtr, freeX509Stack); + + // Use the key & certificates + if (SSL_CTX_use_certificate(context_, cert.get()) != 1) { + return false; + } + if (SSL_CTX_use_PrivateKey(context_, privateKey.get()) != 1) { + return false; + } + for (int i = 0; i < sk_X509_num(caCerts.get()); ++i) { + SSL_CTX_add_extra_chain_cert(context_, sk_X509_value(caCerts.get(), i)); + } + return true; +} + +} diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.h b/Swiften/TLS/OpenSSL/OpenSSLContext.h new file mode 100644 index 0000000..3e735df --- /dev/null +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.h @@ -0,0 +1,43 @@ +#include <openssl/ssl.h> +#include <boost/signal.hpp> +#include <boost/noncopyable.hpp> + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PKCS12Certificate; + + class OpenSSLContext : boost::noncopyable { + public: + OpenSSLContext(); + ~OpenSSLContext(); + + void connect(); + bool setClientCertificate(const PKCS12Certificate& cert); + + void handleDataFromNetwork(const ByteArray&); + void handleDataFromApplication(const ByteArray&); + + public: + boost::signal<void (const ByteArray&)> onDataForNetwork; + boost::signal<void (const ByteArray&)> onDataForApplication; + boost::signal<void ()> onError; + boost::signal<void ()> onConnected; + + private: + static void ensureLibraryInitialized(); + + void doConnect(); + void sendPendingDataToNetwork(); + void sendPendingDataToApplication(); + + private: + enum State { Start, Connecting, Connected, Error }; + + State state_; + SSL_CTX* context_; + SSL* handle_; + BIO* readBIO_; + BIO* writeBIO_; + }; +} diff --git a/Swiften/TLS/PKCS12Certificate.h b/Swiften/TLS/PKCS12Certificate.h new file mode 100644 index 0000000..4b0e708 --- /dev/null +++ b/Swiften/TLS/PKCS12Certificate.h @@ -0,0 +1,37 @@ +#ifndef SWIFTEN_PKCS12Certificate_H +#define SWIFTEN_PKCS12Certificate_H + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class PKCS12Certificate { + public: + PKCS12Certificate() {} + + PKCS12Certificate(const String& filename, const String& password) : password_(password) { + data_.readFromFile(filename); + } + + bool isNull() const { + return data_.isEmpty(); + } + + const ByteArray& getData() const { + return data_; + } + + void setData(const ByteArray& data) { + data_ = data; + } + + const String& getPassword() const { + return password_; + } + + private: + ByteArray data_; + String password_; + }; +} + +#endif |