diff options
author | Remko Tronçon <git@el-tramo.be> | 2010-10-17 12:13:36 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2010-10-21 18:25:00 (GMT) |
commit | 1b58ef2af54456004390a0888c3edf104e3baa99 (patch) | |
tree | dbe4ae29de1b765a88ea704dfaa1c03af4b196b3 /Swiften | |
parent | 07402c4e3451f2084a1c3ddc5bacfb38a66899a7 (diff) | |
download | swift-1b58ef2af54456004390a0888c3edf104e3baa99.zip swift-1b58ef2af54456004390a0888c3edf104e3baa99.tar.bz2 |
Added beginnings of outgoing file transfer to Swiften.
Diffstat (limited to 'Swiften')
76 files changed, 2637 insertions, 82 deletions
diff --git a/Swiften/Base/ByteArray.h b/Swiften/Base/ByteArray.h index 21cfb87..09698ad 100644 --- a/Swiften/Base/ByteArray.h +++ b/Swiften/Base/ByteArray.h @@ -106,6 +106,10 @@ namespace Swift { void readFromFile(const String& file); + void clear() { + data_.clear(); + } + private: std::vector<char> data_; }; diff --git a/Swiften/Base/StartStopper.h b/Swiften/Base/StartStopper.h new file mode 100644 index 0000000..7ea6049 --- /dev/null +++ b/Swiften/Base/StartStopper.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + template<typename T> class StartStopper { + public: + StartStopper(T* target) : target(target) { + target->start(); + } + + ~StartStopper() { + target->stop(); + } + + private: + T* target; + }; +} diff --git a/Swiften/Component/UnitTest/ComponentConnectorTest.cpp b/Swiften/Component/UnitTest/ComponentConnectorTest.cpp index 7b8a4f8..4648365 100644 --- a/Swiften/Component/UnitTest/ComponentConnectorTest.cpp +++ b/Swiften/Component/UnitTest/ComponentConnectorTest.cpp @@ -177,6 +177,7 @@ class ComponentConnectorTest : public CppUnit::TestFixture { void disconnect() { assert(false); } void write(const ByteArray&) { assert(false); } + HostAddressPort getLocalAddress() const { return HostAddressPort(); } boost::optional<HostAddressPort> hostAddressPort; std::vector<HostAddressPort> failingPorts; diff --git a/Swiften/Elements/Bytestreams.h b/Swiften/Elements/Bytestreams.h new file mode 100644 index 0000000..323167c --- /dev/null +++ b/Swiften/Elements/Bytestreams.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <boost/optional.hpp> + +#include "Swiften/JID/JID.h" +#include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class Bytestreams : public Payload, public Shared<Bytestreams> { + public: + struct StreamHost { + StreamHost(const String& host = "", const JID& jid = JID(), int port = -1) : host(host), jid(jid), port(port) {} + + String host; + JID jid; + int port; + }; + + Bytestreams() {} + + const String& getStreamID() const { + return id; + } + + void setStreamID(const String& id) { + this->id = id; + } + + const boost::optional<JID>& getUsedStreamHost() const { + return usedStreamHost; + } + + void setUsedStreamHost(const JID& host) { + usedStreamHost = host; + } + + const std::vector<StreamHost>& getStreamHosts() const { + return streamHosts; + } + + void addStreamHost(const StreamHost& streamHost) { + streamHosts.push_back(streamHost); + } + + private: + String id; + boost::optional<JID> usedStreamHost; + std::vector<StreamHost> streamHosts; + }; +} diff --git a/Swiften/Elements/Form.cpp b/Swiften/Elements/Form.cpp index 9420fb9..41014ba 100644 --- a/Swiften/Elements/Form.cpp +++ b/Swiften/Elements/Form.cpp @@ -10,13 +10,19 @@ namespace Swift { String Form::getFormType() const { + FormField::ref field = getField("FORM_TYPE"); + boost::shared_ptr<HiddenFormField> f = boost::dynamic_pointer_cast<HiddenFormField>(field); + return (f ? f->getValue() : ""); +} + +FormField::ref Form::getField(const String& name) const { foreach(FormField::ref field, fields_) { - boost::shared_ptr<HiddenFormField> f = boost::dynamic_pointer_cast<HiddenFormField>(field); - if (f && f->getName() == "FORM_TYPE") { - return f->getValue(); + if (field->getName() == name) { + return field; } } - return ""; + return FormField::ref(); } + } diff --git a/Swiften/Elements/Form.h b/Swiften/Elements/Form.h index f5826a5..0eb6ef0 100644 --- a/Swiften/Elements/Form.h +++ b/Swiften/Elements/Form.h @@ -40,6 +40,8 @@ namespace Swift { String getFormType() const; + FormField::ref getField(const String& name) const; + private: std::vector<boost::shared_ptr<FormField> > fields_; String title_; diff --git a/Swiften/Elements/IBB.h b/Swiften/Elements/IBB.h new file mode 100644 index 0000000..da9c18a --- /dev/null +++ b/Swiften/Elements/IBB.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h" +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class IBB : public Payload, public Shared<IBB> { + public: + enum Action { + Open, + Close, + Data, + }; + enum StanzaType { + IQStanza, + MessageStanza, + }; + + IBB(Action action = Open, const String& streamID = "") : action(action), streamID(streamID), stanzaType(IQStanza), blockSize(-1), sequenceNumber(-1) { + } + + static IBB::ref createIBBOpen(const String& streamID, int blockSize) { + IBB::ref result(new IBB(Open, streamID)); + result->setBlockSize(blockSize); + return result; + } + + static IBB::ref createIBBData(const String& streamID, int sequenceNumber, const ByteArray& data) { + IBB::ref result(new IBB(Data, streamID)); + result->setSequenceNumber(sequenceNumber); + result->setData(data); + return result; + } + + static IBB::ref createIBBClose(const String& streamID) { + return IBB::ref(new IBB(Close, streamID)); + } + + void setAction(Action action) { + this->action = action; + } + + Action getAction() const { + return action; + } + + void setStanzaType(StanzaType stanzaType) { + this->stanzaType = stanzaType; + } + + StanzaType getStanzaType() const { + return stanzaType; + } + + void setStreamID(const String& id) { + streamID = id; + } + + const String& getStreamID() const { + return streamID; + } + + const ByteArray& getData() const { + return data; + } + + void setData(const ByteArray& data) { + this->data = data; + } + + int getBlockSize() const { + return blockSize; + } + + void setBlockSize(int blockSize) { + this->blockSize = blockSize; + } + + int getSequenceNumber() const { + return sequenceNumber; + } + + void setSequenceNumber(int i) { + sequenceNumber = i; + } + + private: + Action action; + String streamID; + ByteArray data; + StanzaType stanzaType; + int blockSize; + int sequenceNumber; + }; +} diff --git a/Swiften/Elements/StreamInitiation.h b/Swiften/Elements/StreamInitiation.h new file mode 100644 index 0000000..fdf2399 --- /dev/null +++ b/Swiften/Elements/StreamInitiation.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <boost/optional.hpp> + +#include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h" +#include "Swiften/Elements/Payload.h" + +namespace Swift { + class StreamInitiation : public Payload, public Shared<StreamInitiation> { + public: + struct FileInfo { + FileInfo(const String& name = "", const String& description = "", int size = -1) : name(name), description(description), size(size) {} + + String name; + String description; + int size; + }; + + StreamInitiation() : isFileTransfer(true) {} + + const String& getID() const { + return id; + } + + void setID(const String& id) { + this->id = id; + } + + const boost::optional<FileInfo>& getFileInfo() const { + return fileInfo; + } + + void setFileInfo(const FileInfo& info) { + fileInfo = info; + } + + const std::vector<String>& getProvidedMethods() const { + return providedMethods; + } + + void addProvidedMethod(const String& method) { + providedMethods.push_back(method); + } + + void setRequestedMethod(const String& method) { + requestedMethod = method; + } + + const String& getRequestedMethod() const { + return requestedMethod; + } + + bool getIsFileTransfer() const { + return isFileTransfer; + } + + void setIsFileTransfer(bool b) { + isFileTransfer = b; + } + + private: + bool isFileTransfer; + String id; + boost::optional<FileInfo> fileInfo; + std::vector<String> providedMethods; + String requestedMethod; + }; +} diff --git a/Swiften/Elements/UnitTest/FormTest.cpp b/Swiften/Elements/UnitTest/FormTest.cpp index 3852d98..715111b 100644 --- a/Swiften/Elements/UnitTest/FormTest.cpp +++ b/Swiften/Elements/UnitTest/FormTest.cpp @@ -15,6 +15,8 @@ using namespace Swift; class FormTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FormTest); CPPUNIT_TEST(testGetFormType); + CPPUNIT_TEST(testGetFormType_InvalidFormType); + CPPUNIT_TEST(testGetFormType_NoFormType); CPPUNIT_TEST_SUITE_END(); public: @@ -31,6 +33,24 @@ class FormTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(String("jabber:bot"), form.getFormType()); } + + void testGetFormType_InvalidFormType() { + Form form; + + FormField::ref field = FixedFormField::create("jabber:bot"); + field->setName("FORM_TYPE"); + form.addField(field); + + CPPUNIT_ASSERT_EQUAL(String(""), form.getFormType()); + } + + void testGetFormType_NoFormType() { + Form form; + + form.addField(FixedFormField::create("Foo")); + + CPPUNIT_ASSERT_EQUAL(String(""), form.getFormType()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(FormTest); diff --git a/Swiften/Examples/SConscript b/Swiften/Examples/SConscript index 61bedfb..7b9b491 100644 --- a/Swiften/Examples/SConscript +++ b/Swiften/Examples/SConscript @@ -4,6 +4,7 @@ myenv = swiften_env.Clone() SConscript(dirs = [ "SendMessage", + "SendFile", "ConnectivityTest", "LinkLocalTool", "ParserTester", diff --git a/Swiften/Examples/SendFile/.gitignore b/Swiften/Examples/SendFile/.gitignore new file mode 100644 index 0000000..f8b7625 --- /dev/null +++ b/Swiften/Examples/SendFile/.gitignore @@ -0,0 +1 @@ +SendFile diff --git a/Swiften/Examples/SendFile/SConscript b/Swiften/Examples/SendFile/SConscript new file mode 100644 index 0000000..50cbe40 --- /dev/null +++ b/Swiften/Examples/SendFile/SConscript @@ -0,0 +1,13 @@ +Import("env") + +myenv = env.Clone() +myenv.MergeFlags(myenv["SWIFTEN_FLAGS"]) +myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) +myenv.MergeFlags(myenv["BOOST_FLAGS"]) +myenv.MergeFlags(myenv["ZLIB_FLAGS"]) +myenv.MergeFlags(myenv["OPENSSL_FLAGS"]) +myenv.MergeFlags(myenv.get("SQLITE_FLAGS", {})) +myenv.MergeFlags(myenv.get("LIBXML_FLAGS", "")) +myenv.MergeFlags(myenv.get("EXPAT_FLAGS", "")) +myenv.MergeFlags(myenv["PLATFORM_FLAGS"]) +tester = myenv.Program("SendFile", ["SendFile.cpp"]) diff --git a/Swiften/Examples/SendFile/SendFile.cpp b/Swiften/Examples/SendFile/SendFile.cpp new file mode 100644 index 0000000..da0b2fe --- /dev/null +++ b/Swiften/Examples/SendFile/SendFile.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> +#include <boost/filesystem.hpp> + +#include "Swiften/Client/Client.h" +#include "Swiften/Network/BoostTimer.h" +#include "Swiften/EventLoop/MainEventLoop.h" +#include "Swiften/Client/ClientXMLTracer.h" +#include "Swiften/EventLoop/SimpleEventLoop.h" +#include "Swiften/Network/MainBoostIOServiceThread.h" +#include "Swiften/FileTransfer/OutgoingFileTransfer.h" +#include "Swiften/FileTransfer/FileReadBytestream.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" +#include "Swiften/Network/BoostConnectionServer.h" +#include "Swiften/Network/BoostIOServiceThread.h" + +using namespace Swift; + +SimpleEventLoop eventLoop; +int exitCode = 2; + +class FileSender { + public: + FileSender(const JID& jid, const String& password, const JID& recipient, const boost::filesystem::path& file, int port) : jid(jid), password(password), recipient(recipient), file(file), transfer(NULL) { + connectionServer = BoostConnectionServer::create(port, &MainBoostIOServiceThread::getInstance().getIOService()); + socksBytestreamServer = new SOCKS5BytestreamServer(connectionServer); + + client = new Swift::Client(jid, password); + client->onConnected.connect(boost::bind(&FileSender::handleConnected, this)); + client->onError.connect(boost::bind(&FileSender::handleError, this, _1)); + //tracer = new ClientXMLTracer(client); + } + + ~FileSender() { + //delete tracer; + client->onError.disconnect(boost::bind(&FileSender::handleError, this, _1)); + client->onConnected.disconnect(boost::bind(&FileSender::handleConnected, this)); + delete client; + delete socksBytestreamServer; + } + + void start() { + connectionServer->start(); + socksBytestreamServer->start(); + client->connect(); + } + + void stop() { + if (transfer) { + transfer->stop(); + } + client->disconnect(); + socksBytestreamServer->stop(); + connectionServer->stop(); + } + + private: + void handleConnected() { + client->sendPresence(Presence::create()); + transfer = new OutgoingFileTransfer("myid", client->getJID(), recipient, file.filename(), boost::filesystem::file_size(file), "A file", boost::shared_ptr<FileReadBytestream>(new FileReadBytestream(file)), client->getIQRouter(), socksBytestreamServer); + transfer->onFinished.connect(boost::bind(&FileSender::handleFileTransferFinished, this, _1)); + transfer->start(); + } + + void handleError(const ClientError&) { + std::cerr << "Error!" << std::endl; + exit(-1); + } + + void handleFileTransferFinished(const boost::optional<FileTransferError>& error) { + std::cout << "File transfer finished" << std::endl; + if (error) { + exit(-1); + } + else { + exit(0); + } + } + + void exit(int code) { + exitCode = code; + stop(); + eventLoop.stop(); + } + + private: + BoostConnectionServer::ref connectionServer; + SOCKS5BytestreamServer* socksBytestreamServer; + JID jid; + String password; + JID recipient; + boost::filesystem::path file; + Client* client; + ClientXMLTracer* tracer; + OutgoingFileTransfer* transfer; +}; + + +int main(int argc, char* argv[]) { + if (argc != 5) { + std::cerr << "Usage: " << argv[0] << " <jid> <password> <recipient> <file>" << std::endl; + return -1; + } + + JID sender(argv[1]); + JID recipient(argv[3]); + FileSender fileSender(sender, String(argv[2]), recipient, boost::filesystem::path(argv[4]), 8888); + fileSender.start(); + + { + /*BoostTimer::ref timer(BoostTimer::create(30000, &MainBoostIOServiceThread::getInstance().getIOService())); + timer->onTick.connect(boost::bind(&SimpleEventLoop::stop, &eventLoop)); + timer->start();*/ + + eventLoop.run(); + } + + return exitCode; +} diff --git a/Swiften/FileTransfer/ByteArrayReadBytestream.h b/Swiften/FileTransfer/ByteArrayReadBytestream.h new file mode 100644 index 0000000..d459658 --- /dev/null +++ b/Swiften/FileTransfer/ByteArrayReadBytestream.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/FileTransfer/ReadBytestream.h" +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ByteArrayReadBytestream : public ReadBytestream { + public: + ByteArrayReadBytestream(const ByteArray& data) : data(data), position(0) { + } + + virtual ByteArray read(size_t size) { + size_t readSize = size; + if (position + readSize > data.getSize()) { + readSize = data.getSize() - position; + } + ByteArray result(data.getData() + position, readSize); + position += readSize; + return result; + } + + virtual bool isFinished() const { + return position >= data.getSize(); + } + + private: + ByteArray data; + size_t position; + }; +} diff --git a/Swiften/FileTransfer/BytestreamException.h b/Swiften/FileTransfer/BytestreamException.h new file mode 100644 index 0000000..f38ef86 --- /dev/null +++ b/Swiften/FileTransfer/BytestreamException.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <stdexcept> + +namespace Swift { + class BytestreamException : public std::exception { + public: + BytestreamException() { + } + }; +} diff --git a/Swiften/FileTransfer/BytestreamsRequest.h b/Swiften/FileTransfer/BytestreamsRequest.h new file mode 100644 index 0000000..71b93ec --- /dev/null +++ b/Swiften/FileTransfer/BytestreamsRequest.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/Bytestreams.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class BytestreamsRequest : public GenericRequest<Bytestreams>, public Shared<BytestreamsRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<Bytestreams> payload, IQRouter* router) { + return ref(new BytestreamsRequest(jid, payload, router)); + } + + private: + BytestreamsRequest(const JID& jid, boost::shared_ptr<Bytestreams> payload, IQRouter* router) : GenericRequest<Bytestreams>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/FileReadBytestream.cpp b/Swiften/FileTransfer/FileReadBytestream.cpp new file mode 100644 index 0000000..7ed33af --- /dev/null +++ b/Swiften/FileTransfer/FileReadBytestream.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/filesystem/fstream.hpp> +#include <cassert> + +#include "Swiften/FileTransfer/FileReadBytestream.h" + +namespace Swift { + +FileReadBytestream::FileReadBytestream(const boost::filesystem::path& file) : file(file), stream(NULL) { +} + +FileReadBytestream::~FileReadBytestream() { + if (stream) { + stream->close(); + stream = NULL; + } +} + +ByteArray FileReadBytestream::read(size_t size) { + if (!stream) { + stream = new boost::filesystem::ifstream(file, std::ios_base::in|std::ios_base::binary); + } + ByteArray result; + result.resize(size); + assert(stream->good()); + stream->read(result.getData(), size); + result.resize(stream->gcount()); + return result; +} + +bool FileReadBytestream::isFinished() const { + return stream && !stream->good(); +} + +} diff --git a/Swiften/FileTransfer/FileReadBytestream.h b/Swiften/FileTransfer/FileReadBytestream.h new file mode 100644 index 0000000..055e194 --- /dev/null +++ b/Swiften/FileTransfer/FileReadBytestream.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> + +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class FileReadBytestream : public ReadBytestream { + public: + FileReadBytestream(const boost::filesystem::path& file); + ~FileReadBytestream(); + + virtual ByteArray read(size_t size) ; + virtual bool isFinished() const; + + private: + boost::filesystem::path file; + boost::filesystem::ifstream* stream; + }; +} diff --git a/Swiften/FileTransfer/FileTransferError.h b/Swiften/FileTransfer/FileTransferError.h new file mode 100644 index 0000000..b718927 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferError.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class FileTransferError { + public: + enum Type { + UnknownError, + PeerError, + ReadError, + }; + + FileTransferError(Type type = UnknownError) : type(type) {} + + Type getType() const { + return type; + } + + private: + Type type; + }; +} diff --git a/Swiften/FileTransfer/IBBRequest.h b/Swiften/FileTransfer/IBBRequest.h new file mode 100644 index 0000000..7269362 --- /dev/null +++ b/Swiften/FileTransfer/IBBRequest.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/IBB.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class IBBRequest : public GenericRequest<IBB>, public Shared<IBBRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) { + return ref(new IBBRequest(jid, payload, router)); + } + + private: + IBBRequest(const JID& jid, boost::shared_ptr<IBB> payload, IQRouter* router) : GenericRequest<IBB>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/IBBSendSession.cpp b/Swiften/FileTransfer/IBBSendSession.cpp new file mode 100644 index 0000000..7246187 --- /dev/null +++ b/Swiften/FileTransfer/IBBSendSession.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/IBBSendSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/FileTransfer/IBBRequest.h" +#include "Swiften/FileTransfer/BytestreamException.h" + +namespace Swift { + +IBBSendSession::IBBSendSession(const String& id, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router) : id(id), to(to), bytestream(bytestream), router(router), blockSize(4096), sequenceNumber(0), active(false) { +} + +IBBSendSession::~IBBSendSession() { +} + +void IBBSendSession::start() { + IBBRequest::ref request = IBBRequest::create(to, IBB::createIBBOpen(id, blockSize), router); + request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); + active = true; + request->send(); +} + +void IBBSendSession::stop() { + if (active && router->isAvailable()) { + IBBRequest::create(to, IBB::createIBBClose(id), router)->send(); + } + finish(boost::optional<FileTransferError>()); +} + +void IBBSendSession::handleIBBResponse(IBB::ref, const boost::optional<ErrorPayload>& error) { + if (!error) { + if (!bytestream->isFinished()) { + try { + ByteArray data = bytestream->read(blockSize); + IBBRequest::ref request = IBBRequest::create(to, IBB::createIBBData(id, sequenceNumber, data), router); + sequenceNumber++; + request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); + request->send(); + } + catch (const BytestreamException& e) { + finish(FileTransferError(FileTransferError::ReadError)); + } + } + else { + finish(boost::optional<FileTransferError>()); + } + } + else { + finish(FileTransferError(FileTransferError::PeerError)); + } +} + +void IBBSendSession::finish(boost::optional<FileTransferError> error) { + active = false; + onFinished(error); +} + +} diff --git a/Swiften/FileTransfer/IBBSendSession.h b/Swiften/FileTransfer/IBBSendSession.h new file mode 100644 index 0000000..102bae2 --- /dev/null +++ b/Swiften/FileTransfer/IBBSendSession.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/FileTransfer/ReadBytestream.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/IBB.h" +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/FileTransfer/FileTransferError.h" + +namespace Swift { + class IQRouter; + + class IBBSendSession { + public: + IBBSendSession(const String& id, const JID& to, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* router); + ~IBBSendSession(); + + void start(); + void stop(); + + void setBlockSize(int blockSize) { + this->blockSize = blockSize; + } + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + + private: + void handleIBBResponse(IBB::ref, const boost::optional<ErrorPayload>&); + void finish(boost::optional<FileTransferError>); + + private: + String id; + JID to; + boost::shared_ptr<ReadBytestream> bytestream; + IQRouter* router; + int blockSize; + int sequenceNumber; + bool active; + }; +} diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.cpp b/Swiften/FileTransfer/OutgoingFileTransfer.cpp new file mode 100644 index 0000000..9f1dd1b --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransfer.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/OutgoingFileTransfer.h" + +#include <boost/bind.hpp> + +#include "Swiften/FileTransfer/StreamInitiationRequest.h" +#include "Swiften/FileTransfer/BytestreamsRequest.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" +#include "Swiften/FileTransfer/IBBSendSession.h" + +namespace Swift { + +OutgoingFileTransfer::OutgoingFileTransfer(const String& id, const JID& from, const JID& to, const String& name, int size, const String& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer) : id(id), from(from), to(to), name(name), size(size), description(description), bytestream(bytestream), iqRouter(iqRouter), socksServer(socksServer) { +} + +void OutgoingFileTransfer::start() { + StreamInitiation::ref streamInitiation(new StreamInitiation()); + streamInitiation->setID(id); + streamInitiation->setFileInfo(StreamInitiation::FileInfo(name, description, size)); + //streamInitiation->addProvidedMethod("http://jabber.org/protocol/bytestreams"); + streamInitiation->addProvidedMethod("http://jabber.org/protocol/ibb"); + StreamInitiationRequest::ref request = StreamInitiationRequest::create(to, streamInitiation, iqRouter); + request->onResponse.connect(boost::bind(&OutgoingFileTransfer::handleStreamInitiationRequestResponse, this, _1, _2)); + request->send(); +} + +void OutgoingFileTransfer::stop() { +} + +void OutgoingFileTransfer::handleStreamInitiationRequestResponse(StreamInitiation::ref response, const boost::optional<ErrorPayload>& error) { + if (error) { + finish(FileTransferError()); + } + else { + if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") { + socksServer->addBytestream(id, from, to, bytestream); + Bytestreams::ref bytestreams(new Bytestreams()); + bytestreams->setStreamID(id); + HostAddressPort addressPort = socksServer->getAddressPort(); + bytestreams->addStreamHost(Bytestreams::StreamHost(addressPort.getAddress().toString(), from, addressPort.getPort())); + BytestreamsRequest::ref request = BytestreamsRequest::create(to, bytestreams, iqRouter); + request->onResponse.connect(boost::bind(&OutgoingFileTransfer::handleBytestreamsRequestResponse, this, _1, _2)); + request->send(); + } + else if (response->getRequestedMethod() == "http://jabber.org/protocol/ibb") { + ibbSession = boost::shared_ptr<IBBSendSession>(new IBBSendSession(id, to, bytestream, iqRouter)); + ibbSession->onFinished.connect(boost::bind(&OutgoingFileTransfer::handleIBBSessionFinished, this, _1)); + ibbSession->start(); + } + } +} + +void OutgoingFileTransfer::handleBytestreamsRequestResponse(Bytestreams::ref, const boost::optional<ErrorPayload>& error) { + if (error) { + finish(FileTransferError()); + } + //socksServer->onTransferFinished.connect(); +} + +void OutgoingFileTransfer::finish(boost::optional<FileTransferError> error) { + if (ibbSession) { + ibbSession->onFinished.disconnect(boost::bind(&OutgoingFileTransfer::handleIBBSessionFinished, this, _1)); + ibbSession.reset(); + } + socksServer->removeBytestream(id, from, to); + onFinished(error); +} + +void OutgoingFileTransfer::handleIBBSessionFinished(boost::optional<FileTransferError> error) { + finish(error); +} + +} diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.h b/Swiften/FileTransfer/OutgoingFileTransfer.h new file mode 100644 index 0000000..cfbfe21 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransfer.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/FileTransfer/ReadBytestream.h" +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/FileTransfer/FileTransferError.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/StreamInitiation.h" +#include "Swiften/Elements/Bytestreams.h" +#include "Swiften/Elements/ErrorPayload.h" +#include "Swiften/FileTransfer/IBBSendSession.h" + +namespace Swift { + class IQRouter; + class SOCKS5BytestreamServer; + + class OutgoingFileTransfer { + public: + OutgoingFileTransfer(const String& id, const JID& from, const JID& to, const String& name, int size, const String& description, boost::shared_ptr<ReadBytestream> bytestream, IQRouter* iqRouter, SOCKS5BytestreamServer* socksServer); + + void start(); + void stop(); + + boost::signal<void (const boost::optional<FileTransferError>&)> onFinished; + + private: + void handleStreamInitiationRequestResponse(StreamInitiation::ref, const boost::optional<ErrorPayload>&); + void handleBytestreamsRequestResponse(Bytestreams::ref, const boost::optional<ErrorPayload>&); + void finish(boost::optional<FileTransferError> error); + void handleIBBSessionFinished(boost::optional<FileTransferError> error); + + private: + String id; + JID from; + JID to; + String name; + int size; + String description; + boost::shared_ptr<ReadBytestream> bytestream; + IQRouter* iqRouter; + SOCKS5BytestreamServer* socksServer; + boost::shared_ptr<IBBSendSession> ibbSession; + }; +} diff --git a/Swiften/FileTransfer/ReadBytestream.cpp b/Swiften/FileTransfer/ReadBytestream.cpp new file mode 100644 index 0000000..705906c --- /dev/null +++ b/Swiften/FileTransfer/ReadBytestream.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + +ReadBytestream::~ReadBytestream() { +} + +} diff --git a/Swiften/FileTransfer/ReadBytestream.h b/Swiften/FileTransfer/ReadBytestream.h new file mode 100644 index 0000000..4da2bc2 --- /dev/null +++ b/Swiften/FileTransfer/ReadBytestream.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Base/ByteArray.h" + +namespace Swift { + class ReadBytestream { + public: + virtual ~ReadBytestream(); + virtual ByteArray read(size_t size) = 0; + virtual bool isFinished() const = 0; + }; +} diff --git a/Swiften/FileTransfer/SConscript b/Swiften/FileTransfer/SConscript new file mode 100644 index 0000000..f42dd88 --- /dev/null +++ b/Swiften/FileTransfer/SConscript @@ -0,0 +1,13 @@ +Import("swiften_env") + +sources = [ + "OutgoingFileTransfer.cpp", + "ReadBytestream.cpp", + "FileReadBytestream.cpp", + "SOCKS5BytestreamServer.cpp", + "SOCKS5BytestreamServerSession.cpp", + "SOCKS5BytestreamRegistry.cpp", + "IBBSendSession.cpp", + ] + +swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.StaticObject(sources)) diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp new file mode 100644 index 0000000..a4715a0 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" + +namespace Swift { + +SOCKS5BytestreamRegistry::SOCKS5BytestreamRegistry() { +} + +void SOCKS5BytestreamRegistry::addBytestream(const String& destination, boost::shared_ptr<ReadBytestream> byteStream) { + byteStreams[destination] = byteStream; +} + +void SOCKS5BytestreamRegistry::removeBytestream(const String& destination) { + byteStreams.erase(destination); +} + +boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getBytestream(const String& destination) const { + BytestreamMap::const_iterator i = byteStreams.find(destination); + if (i != byteStreams.end()) { + return i->second; + } + return boost::shared_ptr<ReadBytestream>(); +} + + +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h new file mode 100644 index 0000000..1afd03f --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> + +#include "Swiften/Base/String.h" +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class SOCKS5BytestreamRegistry { + public: + SOCKS5BytestreamRegistry(); + + boost::shared_ptr<ReadBytestream> getBytestream(const String& destination) const; + void addBytestream(const String& destination, boost::shared_ptr<ReadBytestream> byteStream); + void removeBytestream(const String& destination); + + private: + typedef std::map<String, boost::shared_ptr<ReadBytestream> > BytestreamMap; + BytestreamMap byteStreams; + }; +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp new file mode 100644 index 0000000..58506f3 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/SOCKS5BytestreamServer.h" + +#include <boost/bind.hpp> + +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/StringCodecs/SHA1.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" + +namespace Swift { + +SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer) : connectionServer(connectionServer) { +} + +void SOCKS5BytestreamServer::start() { + connectionServer->onNewConnection.connect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); +} + +void SOCKS5BytestreamServer::stop() { + connectionServer->onNewConnection.disconnect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); +} + +void SOCKS5BytestreamServer::addBytestream(const String& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { + bytestreams.addBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); +} + +void SOCKS5BytestreamServer::removeBytestream(const String& id, const JID& from, const JID& to) { + bytestreams.removeBytestream(getSOCKSDestinationAddress(id, from, to)); +} + +String SOCKS5BytestreamServer::getSOCKSDestinationAddress(const String& id, const JID& from, const JID& to) { + return Hexify::hexify(SHA1::getHash(ByteArray(id + from.toString() + to.toString()))); +} + +void SOCKS5BytestreamServer::handleNewConnection(boost::shared_ptr<Connection> connection) { + boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, &bytestreams)); + sessions.push_back(session); + session->start(); +} + +HostAddressPort SOCKS5BytestreamServer::getAddressPort() const { + return connectionServer->getAddressPort(); +} + +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.h b/Swiften/FileTransfer/SOCKS5BytestreamServer.h new file mode 100644 index 0000000..35a8d4f --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> + +#include "Swiften/Network/ConnectionServer.h" +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swiften/FileTransfer/ReadBytestream.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" + +namespace Swift { + class SOCKS5BytestreamServerSession; + + class SOCKS5BytestreamServer { + public: + SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer); + + HostAddressPort getAddressPort() const; + + void start(); + void stop(); + + void addBytestream(const String& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); + void removeBytestream(const String& id, const JID& from, const JID& to); + + /*protected: + boost::shared_ptr<ReadBytestream> getBytestream(const String& dest);*/ + + private: + void handleNewConnection(boost::shared_ptr<Connection> connection); + + static String getSOCKSDestinationAddress(const String& id, const JID& from, const JID& to); + + private: + friend class SOCKS5BytestreamServerSession; + + boost::shared_ptr<ConnectionServer> connectionServer; + SOCKS5BytestreamRegistry bytestreams; + std::vector<boost::shared_ptr<SOCKS5BytestreamServerSession> > sessions; + }; +} + diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp new file mode 100644 index 0000000..d22845a --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" + +#include <boost/bind.hpp> + +#include "Swiften/Base/ByteArray.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" +#include "Swiften/FileTransfer/BytestreamException.h" + +namespace Swift { + +SOCKS5BytestreamServerSession::SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* bytestreams) : connection(connection), bytestreams(bytestreams), state(Initial), chunkSize(4096) { +} + +SOCKS5BytestreamServerSession::~SOCKS5BytestreamServerSession() { + if (state != Finished && state != Initial) { + std::cerr << "Warning: SOCKS5BytestremServerSession unfinished" << std::endl; + finish(false); + } +} + +void SOCKS5BytestreamServerSession::start() { + connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + state = WaitingForAuthentication; +} + +void SOCKS5BytestreamServerSession::stop() { + finish(false); +} + +void SOCKS5BytestreamServerSession::handleDataRead(const ByteArray& data) { + unprocessedData += data; + process(); +} + +void SOCKS5BytestreamServerSession::process() { + if (state == WaitingForAuthentication) { + if (unprocessedData.getSize() >= 2) { + int authCount = unprocessedData[1]; + int i = 2; + while (i < 2 + authCount && i < unprocessedData.getSize()) { + // Skip authentication mechanism + ++i; + } + if (i == 2 + authCount) { + // Authentication message is complete + if (i != unprocessedData.getSize()) { + std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + } + unprocessedData.clear(); + connection->write(ByteArray("\x05\x00", 2)); + state = WaitingForRequest; + } + } + } + else if (state == WaitingForRequest) { + if (unprocessedData.getSize() >= 5) { + ByteArray requestID; + int i = 5; + int hostnameSize = unprocessedData[4]; + while (i < 5 + hostnameSize && i < unprocessedData.getSize()) { + requestID += unprocessedData[i]; + ++i; + } + // Skip the port: + i += 2; + if (i >= unprocessedData.getSize()) { + if (i != unprocessedData.getSize()) { + std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + } + bytestream = bytestreams->getBytestream(requestID.toString()); + ByteArray result("\x05", 1); + result += bytestream ? 0x0 : 0x4; + result += ByteArray("\x00\x03", 2); + result += static_cast<char>(requestID.getSize()); + result += requestID + ByteArray("\x00\x00", 2); + if (!bytestream) { + connection->write(result); + finish(true); + } + else { + state = SendingData; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->write(result); + } + } + } + } +} + +void SOCKS5BytestreamServerSession::sendData() { + if (!bytestream->isFinished()) { + try { + connection->write(bytestream->read(chunkSize)); + } + catch (const BytestreamException& e) { + finish(true); + } + } + else { + finish(false); + } +} + +void SOCKS5BytestreamServerSession::finish(bool error) { + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + bytestream.reset(); + state = Finished; + onFinished(error); +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h new file mode 100644 index 0000000..f430f5d --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swiften/Base/boost_bsignals.h" +#include "Swiften/Network/Connection.h" +#include "Swiften/FileTransfer/ReadBytestream.h" + +namespace Swift { + class SOCKS5BytestreamRegistry; + + class SOCKS5BytestreamServerSession { + public: + enum State { + Initial, + WaitingForAuthentication, + WaitingForRequest, + SendingData, + Finished, + }; + + SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* registry); + ~SOCKS5BytestreamServerSession(); + + void setChunkSize(int chunkSize) { + this->chunkSize = chunkSize; + } + + void start(); + void stop(); + + boost::signal<void (bool /* error */)> onFinished; + + private: + void finish(bool error); + void process(); + void handleDataRead(const ByteArray&); + void sendData(); + + private: + boost::shared_ptr<Connection> connection; + SOCKS5BytestreamRegistry* bytestreams; + ByteArray unprocessedData; + State state; + int chunkSize; + boost::shared_ptr<ReadBytestream> bytestream; + }; +} diff --git a/Swiften/FileTransfer/StreamInitiationRequest.h b/Swiften/FileTransfer/StreamInitiationRequest.h new file mode 100644 index 0000000..64f51f2 --- /dev/null +++ b/Swiften/FileTransfer/StreamInitiationRequest.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Queries/GenericRequest.h" +#include "Swiften/Elements/StreamInitiation.h" +#include "Swiften/Base/Shared.h" + +namespace Swift { + class StreamInitiationRequest : public GenericRequest<StreamInitiation>, public Shared<StreamInitiationRequest> { + public: + static ref create(const JID& jid, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) { + return ref(new StreamInitiationRequest(jid, payload, router)); + } + + private: + StreamInitiationRequest(const JID& jid, boost::shared_ptr<StreamInitiation> payload, IQRouter* router) : GenericRequest<StreamInitiation>(IQ::Set, jid, payload, router) { + } + }; +} diff --git a/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp new file mode 100644 index 0000000..9052439 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/IBBSendSessionTest.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <vector> +#include <boost/bind.hpp> + +#include "Swiften/FileTransfer/IBBSendSession.h" +#include "Swiften/FileTransfer/ByteArrayReadBytestream.h" +#include "Swiften/Queries/IQRouter.h" +#include "Swiften/Client/DummyStanzaChannel.h" + +using namespace Swift; + +class IBBSendSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IBBSendSessionTest); + CPPUNIT_TEST(testStart); + CPPUNIT_TEST(testStart_ResponseStartsSending); + CPPUNIT_TEST(testResponseContinuesSending); + CPPUNIT_TEST(testRespondToAllFinishes); + CPPUNIT_TEST(testErrorResponseFinishesWithError); + CPPUNIT_TEST(testStopDuringSessionCloses); + CPPUNIT_TEST(testStopAfterFinishedDoesNotClose); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + bytestream = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + } + + void tearDown() { + delete iqRouter; + delete stanzaChannel; + } + + void testStart() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(1234); + + testling->start(); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(stanzaChannel->sentStanzas.size())); + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(0, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[0]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Open, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(1234, ibb->getBlockSize()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testStart_ResponseStartsSending() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(1, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[1]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Data, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(ByteArray("abc"), ibb->getData()); + CPPUNIT_ASSERT_EQUAL(0, ibb->getSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testResponseContinuesSending() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(stanzaChannel->sentStanzas.size())); + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(2, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[2]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Data, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(ByteArray("def"), ibb->getData()); + CPPUNIT_ASSERT_EQUAL(1, ibb->getSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + } + + void testRespondToAllFinishes() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + } + + void testErrorResponseFinishesWithError() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + stanzaChannel->onIQReceived(IQ::createError(JID("baz@fum.com/foo"), stanzaChannel->sentStanzas[0]->getID())); + + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(error); + } + + void testStopDuringSessionCloses() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(3); + testling->start(); + testling->stop(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + CPPUNIT_ASSERT(stanzaChannel->isRequestAtIndex<IBB>(1, JID("foo@bar.com/baz"), IQ::Set)); + IBB::ref ibb = stanzaChannel->sentStanzas[1]->getPayload<IBB>(); + CPPUNIT_ASSERT_EQUAL(IBB::Close, ibb->getAction()); + CPPUNIT_ASSERT_EQUAL(String("myid"), ibb->getStreamID()); + CPPUNIT_ASSERT(finished); + CPPUNIT_ASSERT(!error); + } + + void testStopAfterFinishedDoesNotClose() { + std::auto_ptr<IBBSendSession> testling = createSession("foo@bar.com/baz"); + testling->setBlockSize(16); + testling->start(); + stanzaChannel->onIQReceived(createIBBResult()); + stanzaChannel->onIQReceived(createIBBResult()); + CPPUNIT_ASSERT(finished); + + testling->stop(); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(stanzaChannel->sentStanzas.size())); + } + + private: + IQ::ref createIBBResult() { + return IQ::createResult(JID("baz@fum.com/dum"), stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getID(), boost::shared_ptr<IBB>()); + } + + private: + std::auto_ptr<IBBSendSession> createSession(const String& to) { + std::auto_ptr<IBBSendSession> session(new IBBSendSession("myid", JID(to), bytestream, iqRouter)); + session->onFinished.connect(boost::bind(&IBBSendSessionTest::handleFinished, this, _1)); + return session; + } + + void handleFinished(boost::optional<FileTransferError> error) { + finished = true; + this->error = error; + } + + private: + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + bool finished; + boost::optional<FileTransferError> error; + boost::shared_ptr<ByteArrayReadBytestream> bytestream; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IBBSendSessionTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp new file mode 100644 index 0000000..126f971 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/bind.hpp> + +#include "Swiften/FileTransfer/SOCKS5BytestreamServerSession.h" +#include "Swiften/FileTransfer/ByteArrayReadBytestream.h" +#include "Swiften/FileTransfer/SOCKS5BytestreamRegistry.h" +#include "Swiften/Network/DummyConnection.h" +#include "Swiften/EventLoop/DummyEventLoop.h" +#include "Swiften/Base/StartStopper.h" + +using namespace Swift; + +class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SOCKS5BytestreamServerSessionTest); + CPPUNIT_TEST(testAuthenticate); + CPPUNIT_TEST(testAuthenticate_Chunked); + CPPUNIT_TEST(testRequest); + CPPUNIT_TEST(testRequest_UnknownBytestream); + CPPUNIT_TEST(testReceiveData); + CPPUNIT_TEST(testReceiveData_Chunked); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + receivedDataChunks = 0; + eventLoop = new DummyEventLoop(); + connection = boost::shared_ptr<DummyConnection>(new DummyConnection()); + connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); + stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(ByteArray("abcdefg"))); + } + + void tearDown() { + connection.reset(); + delete eventLoop; + } + + void testAuthenticate() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + + receive(ByteArray("\x05\x02\x01\x02")); + + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + } + + void testAuthenticate_Chunked() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + + receive(ByteArray("\x05\x02\x01")); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(receivedData.getSize())); + receive(ByteArray("\x01")); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00", 2), receivedData); + } + + void testRequest() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("abcdef", stream1); + authenticate(); + + ByteArray hostname("abcdef"); + receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getSize() + hostname + ByteArray("\x00\x00", 2)); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x00\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13), ByteArray(receivedData.getData(), 13)); + } + + void testRequest_UnknownBytestream() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + authenticate(); + + ByteArray hostname("abcdef"); + receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getSize() + hostname + ByteArray("\x00\x00", 2)); + CPPUNIT_ASSERT_EQUAL(ByteArray("\x05\x04\x00\x03\x06\x61\x62\x63\x64\x65\x66\x00\x00", 13), receivedData); + } + + void testReceiveData() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("abcdef", stream1); + authenticate(); + request("abcdef"); + eventLoop->processEvents(); + skipHeader("abcdef"); + + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT_EQUAL(2, receivedDataChunks); + } + + void testReceiveData_Chunked() { + std::auto_ptr<SOCKS5BytestreamServerSession> testling(createSession()); + testling->setChunkSize(3); + StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); + bytestreams.addBytestream("abcdef", stream1); + authenticate(); + request("abcdef"); + eventLoop->processEvents(); + + skipHeader("abcdef"); + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefg"), receivedData); + CPPUNIT_ASSERT_EQUAL(4, receivedDataChunks); + } + + private: + void receive(const ByteArray& data) { + connection->receive(data); + eventLoop->processEvents(); + } + + void authenticate() { + receive(ByteArray("\x05\x02\x01\x02")); + receivedData.clear(); + receivedDataChunks = 0; + } + + void request(const String& hostname) { + receive(ByteArray("\x05\x01\x00\x03", 4) + hostname.getUTF8Size() + hostname + ByteArray("\x00\x00", 2)); + } + + void skipHeader(const String& hostname) { + int headerSize = 7 + hostname.getUTF8Size(); + receivedData = ByteArray(receivedData.getData() + headerSize, receivedData.getSize() - headerSize); + } + + + void handleDataWritten(const ByteArray& data) { + receivedData += data; + receivedDataChunks++; + } + + private: + SOCKS5BytestreamServerSession* createSession() { + SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, &bytestreams); + return session; + } + + private: + DummyEventLoop* eventLoop; + SOCKS5BytestreamRegistry bytestreams; + boost::shared_ptr<DummyConnection> connection; + ByteArray receivedData; + int receivedDataChunks; + boost::shared_ptr<ByteArrayReadBytestream> stream1; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SOCKS5BytestreamServerSessionTest); diff --git a/Swiften/Network/BoostConnection.cpp b/Swiften/Network/BoostConnection.cpp index ccaae8a..8cd19f2 100644 --- a/Swiften/Network/BoostConnection.cpp +++ b/Swiften/Network/BoostConnection.cpp @@ -102,7 +102,7 @@ void BoostConnection::handleSocketRead(const boost::system::error_code& error, s void BoostConnection::handleDataWritten(const boost::system::error_code& error) { if (!error) { - return; + MainEventLoop::postEvent(boost::ref(onDataWritten), shared_from_this()); } if (error == boost::asio::error::eof) { MainEventLoop::postEvent(boost::bind(boost::ref(onDisconnected), boost::optional<Error>()), shared_from_this()); @@ -112,4 +112,9 @@ void BoostConnection::handleDataWritten(const boost::system::error_code& error) } } +HostAddressPort BoostConnection::getLocalAddress() const { + return HostAddressPort(socket_.local_endpoint()); +} + + } diff --git a/Swiften/Network/BoostConnection.h b/Swiften/Network/BoostConnection.h index 8d3d444..4f5352f 100644 --- a/Swiften/Network/BoostConnection.h +++ b/Swiften/Network/BoostConnection.h @@ -39,6 +39,8 @@ namespace Swift { return socket_; } + HostAddressPort getLocalAddress() const; + private: BoostConnection(boost::asio::io_service* ioService); diff --git a/Swiften/Network/BoostConnectionServer.cpp b/Swiften/Network/BoostConnectionServer.cpp index febe6c9..03ae19c 100644 --- a/Swiften/Network/BoostConnectionServer.cpp +++ b/Swiften/Network/BoostConnectionServer.cpp @@ -71,4 +71,13 @@ void BoostConnectionServer::handleAccept(boost::shared_ptr<BoostConnection> newC } } +HostAddressPort BoostConnectionServer::getAddressPort() const { + if (acceptor_) { + return HostAddressPort(acceptor_->local_endpoint()); + } + else { + return HostAddressPort(); + } +} + } diff --git a/Swiften/Network/BoostConnectionServer.h b/Swiften/Network/BoostConnectionServer.h index 3a3c096..abcb3af 100644 --- a/Swiften/Network/BoostConnectionServer.h +++ b/Swiften/Network/BoostConnectionServer.h @@ -28,10 +28,12 @@ namespace Swift { static ref create(int port, boost::asio::io_service* ioService) { return ref(new BoostConnectionServer(port, ioService)); } - + void start(); void stop(); + virtual HostAddressPort getAddressPort() const; + boost::signal<void (boost::optional<Error>)> onStopped; private: diff --git a/Swiften/Network/Connection.h b/Swiften/Network/Connection.h index 34f3ade..8c30ad7 100644 --- a/Swiften/Network/Connection.h +++ b/Swiften/Network/Connection.h @@ -10,10 +10,9 @@ #include "Swiften/Base/ByteArray.h" #include "Swiften/Base/String.h" +#include "Swiften/Network/HostAddressPort.h" namespace Swift { - class HostAddressPort; - class Connection { public: enum Error { @@ -29,9 +28,12 @@ namespace Swift { virtual void disconnect() = 0; virtual void write(const ByteArray& data) = 0; + virtual HostAddressPort getLocalAddress() const = 0; + public: boost::signal<void (bool /* error */)> onConnectFinished; boost::signal<void (const boost::optional<Error>&)> onDisconnected; boost::signal<void (const ByteArray&)> onDataRead; + boost::signal<void ()> onDataWritten; }; } diff --git a/Swiften/Network/ConnectionServer.h b/Swiften/Network/ConnectionServer.h index 5597d1b..8129372 100644 --- a/Swiften/Network/ConnectionServer.h +++ b/Swiften/Network/ConnectionServer.h @@ -10,12 +10,15 @@ #include "Swiften/Base/boost_bsignals.h" #include "Swiften/Network/Connection.h" +#include "Swiften/Network/HostAddressPort.h" namespace Swift { class ConnectionServer { public: virtual ~ConnectionServer(); + virtual HostAddressPort getAddressPort() const = 0; + boost::signal<void (boost::shared_ptr<Connection>)> onNewConnection; }; } diff --git a/Swiften/Network/DummyConnection.h b/Swiften/Network/DummyConnection.h index d1657d5..576965f 100644 --- a/Swiften/Network/DummyConnection.h +++ b/Swiften/Network/DummyConnection.h @@ -15,32 +15,35 @@ #include "Swiften/EventLoop/EventOwner.h" namespace Swift { - class DummyConnection : - public Connection, - public EventOwner, - public boost::enable_shared_from_this<DummyConnection> { + class DummyConnection : public Connection, public EventOwner, public boost::enable_shared_from_this<DummyConnection> { + public: + void listen() { + assert(false); + } - void listen() { - assert(false); - } + void connect(const HostAddressPort&) { + assert(false); + } - void connect(const HostAddressPort&) { - assert(false); - } + void disconnect() { + //assert(false); + } - void disconnect() { - assert(false); - } + void write(const ByteArray& data) { + MainEventLoop::postEvent(boost::ref(onDataWritten), shared_from_this()); + onDataSent(data); + } - void write(const ByteArray& data) { - onDataWritten(data); - } + void receive(const ByteArray& data) { + MainEventLoop::postEvent(boost::bind(boost::ref(onDataRead), ByteArray(data)), shared_from_this()); + } - void receive(const ByteArray& data) { - MainEventLoop::postEvent(boost::bind( - boost::ref(onDataRead), ByteArray(data)), shared_from_this()); - } + HostAddressPort getLocalAddress() const { + return localAddress; + } - boost::signal<void (const ByteArray&)> onDataWritten; + boost::signal<void (const ByteArray&)> onDataSent; + + HostAddressPort localAddress; }; } diff --git a/Swiften/Network/FakeConnection.h b/Swiften/Network/FakeConnection.h index 60b8c94..a89466f 100644 --- a/Swiften/Network/FakeConnection.h +++ b/Swiften/Network/FakeConnection.h @@ -36,6 +36,10 @@ namespace Swift { assert(false); } + virtual HostAddressPort getLocalAddress() const { + return HostAddressPort(); + } + void setError(const Error& e) { error = boost::optional<Error>(e); state = DisconnectedWithError; diff --git a/Swiften/Network/HostAddress.cpp b/Swiften/Network/HostAddress.cpp index e299429..b3876c0 100644 --- a/Swiften/Network/HostAddress.cpp +++ b/Swiften/Network/HostAddress.cpp @@ -9,8 +9,8 @@ #include <boost/numeric/conversion/cast.hpp> #include <boost/lexical_cast.hpp> #include <cassert> -#include <sstream> -#include <iomanip> +#include <stdexcept> +#include <boost/array.hpp> #include "Swiften/Base/foreach.h" #include "Swiften/Base/String.h" @@ -18,50 +18,43 @@ namespace Swift { HostAddress::HostAddress() { - for (int i = 0; i < 4; ++i) { - address_.push_back(0); - } } HostAddress::HostAddress(const String& address) { - std::vector<String> components = address.split('.'); - assert(components.size() == 4); - foreach(const String& component, components) { - address_.push_back(boost::lexical_cast<int>(component.getUTF8String())); + try { + address_ = boost::asio::ip::address::from_string(address.getUTF8String()); + } + catch (const std::exception& t) { } } 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]) << ":"; + if (length == 4) { + boost::array<unsigned char, 4> data; + for (int i = 0; i < length; ++i) { + data[i] = address[i]; } - 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(); + address_ = boost::asio::ip::address(boost::asio::ip::address_v4(data)); } else { - assert(false); - return ""; + boost::array<unsigned char, 16> data; + for (int i = 0; i < length; ++i) { + data[i] = address[i]; + } + address_ = boost::asio::ip::address(boost::asio::ip::address_v6(data)); } } +HostAddress::HostAddress(const boost::asio::ip::address& address) : address_(address) { +} + +std::string HostAddress::toString() const { + return address_.to_string(); +} + +bool HostAddress::isValid() const { + return !(address_.is_v4() && address_.to_v4().to_ulong() == 0); +} + } diff --git a/Swiften/Network/HostAddress.h b/Swiften/Network/HostAddress.h index cf2db07..6e2bde0 100644 --- a/Swiften/Network/HostAddress.h +++ b/Swiften/Network/HostAddress.h @@ -8,6 +8,7 @@ #include <string> #include <vector> +#include <boost/asio.hpp> namespace Swift { class String; @@ -17,10 +18,7 @@ namespace Swift { HostAddress(); HostAddress(const String&); HostAddress(const unsigned char* address, int length); - - const std::vector<unsigned char>& getRawAddress() const { - return address_; - } + HostAddress(const boost::asio::ip::address& address); std::string toString() const; @@ -28,7 +26,9 @@ namespace Swift { return address_ == o.address_; } + bool isValid() const; + private: - std::vector<unsigned char> address_; + boost::asio::ip::address address_; }; } diff --git a/Swiften/Network/HostAddressPort.h b/Swiften/Network/HostAddressPort.h index cf24a26..6883380 100644 --- a/Swiften/Network/HostAddressPort.h +++ b/Swiften/Network/HostAddressPort.h @@ -4,17 +4,24 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_HostAddressPort_H -#define SWIFTEN_HostAddressPort_H +#pragma once + +#include <boost/asio.hpp> #include "Swiften/Network/HostAddress.h" namespace Swift { class HostAddressPort { public: - HostAddressPort(const HostAddress& address, int port) : address_(address), port_(port) { + HostAddressPort(const HostAddress& address = HostAddress(), int port = -1) : address_(address), port_(port) { + } + + HostAddressPort(const boost::asio::ip::tcp::endpoint& endpoint) { + address_ = HostAddress(endpoint.address()); + port_ = endpoint.port(); } + const HostAddress& getAddress() const { return address_; } @@ -27,10 +34,12 @@ namespace Swift { return address_ == o.address_ && port_ == o.port_; } + bool isValid() const { + return address_.isValid() && port_ > 0; + } + private: HostAddress address_; int port_; }; } - -#endif diff --git a/Swiften/Network/UnitTest/ConnectorTest.cpp b/Swiften/Network/UnitTest/ConnectorTest.cpp index 2e396b3..07e520c 100644 --- a/Swiften/Network/UnitTest/ConnectorTest.cpp +++ b/Swiften/Network/UnitTest/ConnectorTest.cpp @@ -268,6 +268,7 @@ class ConnectorTest : public CppUnit::TestFixture { } } + HostAddressPort getLocalAddress() const { return HostAddressPort(); } void disconnect() { assert(false); } void write(const ByteArray&) { assert(false); } diff --git a/Swiften/Network/UnitTest/HostAddressTest.cpp b/Swiften/Network/UnitTest/HostAddressTest.cpp index 1cc31ad..45793fa 100644 --- a/Swiften/Network/UnitTest/HostAddressTest.cpp +++ b/Swiften/Network/UnitTest/HostAddressTest.cpp @@ -15,8 +15,11 @@ using namespace Swift; class HostAddressTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(HostAddressTest); CPPUNIT_TEST(testConstructor); + CPPUNIT_TEST(testConstructor_Invalid); + CPPUNIT_TEST(testConstructor_InvalidString); CPPUNIT_TEST(testToString); CPPUNIT_TEST(testToString_IPv6); + CPPUNIT_TEST(testToString_Invalid); CPPUNIT_TEST_SUITE_END(); public: @@ -24,6 +27,19 @@ class HostAddressTest : public CppUnit::TestFixture { HostAddress testling("192.168.1.254"); CPPUNIT_ASSERT_EQUAL(std::string("192.168.1.254"), testling.toString()); + CPPUNIT_ASSERT(testling.isValid()); + } + + void testConstructor_Invalid() { + HostAddress testling; + + CPPUNIT_ASSERT(!testling.isValid()); + } + + void testConstructor_InvalidString() { + HostAddress testling("invalid"); + + CPPUNIT_ASSERT(!testling.isValid()); } void testToString() { @@ -37,7 +53,13 @@ class HostAddressTest : public CppUnit::TestFixture { 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_ASSERT_EQUAL(std::string("102:304:506:708:90a:b0c:d0e:f11"), testling.toString()); + } + + void testToString_Invalid() { + HostAddress testling; + + CPPUNIT_ASSERT_EQUAL(std::string("0.0.0.0"), testling.toString()); } }; diff --git a/Swiften/Parser/GenericPayloadParserFactory.h b/Swiften/Parser/GenericPayloadParserFactory.h index e7e070d..05b996a 100644 --- a/Swiften/Parser/GenericPayloadParserFactory.h +++ b/Swiften/Parser/GenericPayloadParserFactory.h @@ -4,8 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_GENERICPAYLOADPARSERFACTORY_H -#define SWIFTEN_GENERICPAYLOADPARSERFACTORY_H +#pragma once #include "Swiften/Parser/PayloadParserFactory.h" #include "Swiften/Base/String.h" @@ -18,7 +17,7 @@ namespace Swift { 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); + return (tag_.isEmpty() ? true : element == tag_) && (xmlns_.isEmpty() ? true : xmlns_ == ns); } virtual PayloadParser* createPayloadParser() { @@ -30,5 +29,3 @@ namespace Swift { String xmlns_; }; } - -#endif diff --git a/Swiften/Parser/PayloadParsers/BytestreamsParser.cpp b/Swiften/Parser/PayloadParsers/BytestreamsParser.cpp new file mode 100644 index 0000000..154a925 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BytestreamsParser.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/BytestreamsParser.h" + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Base/foreach.h" + +namespace Swift { + +BytestreamsParser::BytestreamsParser() : level(TopLevel) { +} + +BytestreamsParser::~BytestreamsParser() { +} + +void BytestreamsParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level == TopLevel) { + getPayloadInternal()->setStreamID(attributes.getAttribute("sid")); + } + else if (level == PayloadLevel) { + if (element == "streamhost") { + try { + getPayloadInternal()->addStreamHost(Bytestreams::StreamHost(attributes.getAttribute("host"), JID(attributes.getAttribute("jid")), boost::lexical_cast<int>(attributes.getAttribute("port")))); + } + catch (boost::bad_lexical_cast& e) { + } + } + else if (element == "streamhost-used") { + getPayloadInternal()->setUsedStreamHost(JID(attributes.getAttribute("jid"))); + } + } + ++level; +} + +void BytestreamsParser::handleEndElement(const String&, const String&) { + --level; +} + +void BytestreamsParser::handleCharacterData(const String&) { +} + + +} diff --git a/Swiften/Parser/PayloadParsers/BytestreamsParser.h b/Swiften/Parser/PayloadParsers/BytestreamsParser.h new file mode 100644 index 0000000..a45baa4 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BytestreamsParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/Bytestreams.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class BytestreamsParser : public GenericPayloadParser<Bytestreams> { + public: + BytestreamsParser(); + ~BytestreamsParser(); + + 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; + }; +} diff --git a/Swiften/Parser/PayloadParsers/BytestreamsParserFactory.h b/Swiften/Parser/PayloadParsers/BytestreamsParserFactory.h new file mode 100644 index 0000000..8defd45 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/BytestreamsParserFactory.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BytestreamsParser.h" + +namespace Swift { + class BytestreamsParserFactory : public GenericPayloadParserFactory<BytestreamsParser> { + public: + BytestreamsParserFactory() : GenericPayloadParserFactory<BytestreamsParser>("query", "http://jabber.org/protocol/bytestreams") {} + }; +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index de0e71c..90e9038 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -27,6 +27,9 @@ #include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h" #include "Swiften/Parser/PayloadParsers/FormParserFactory.h" #include "Swiften/Parser/PayloadParsers/CommandParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StreamInitiationParserFactory.h" +#include "Swiften/Parser/PayloadParsers/BytestreamsParserFactory.h" +#include "Swiften/Parser/PayloadParsers/IBBParserFactory.h" #include "Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h" #include "Swiften/Parser/PayloadParsers/VCardParserFactory.h" #include "Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h" @@ -40,6 +43,7 @@ using namespace boost; namespace Swift { FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { + factories_.push_back(shared_ptr<PayloadParserFactory>(new IBBParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new StatusShowParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new BodyParserFactory())); @@ -58,6 +62,8 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelsCatalogParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new FormParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new CommandParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new StreamInitiationParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new BytestreamsParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardUpdateParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this))); diff --git a/Swiften/Parser/PayloadParsers/IBBParser.cpp b/Swiften/Parser/PayloadParsers/IBBParser.cpp new file mode 100644 index 0000000..b2b4929 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/IBBParser.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/IBBParser.h" + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +IBBParser::IBBParser() : level(TopLevel) { +} + +IBBParser::~IBBParser() { +} + +void IBBParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) { + if (level == TopLevel) { + if (element == "data") { + getPayloadInternal()->setAction(IBB::Data); + getPayloadInternal()->setStreamID(attributes.getAttribute("sid")); + try { + getPayloadInternal()->setSequenceNumber(boost::lexical_cast<int>(attributes.getAttribute("seq"))); + } + catch (boost::bad_lexical_cast& e) { + } + } + else if (element == "open") { + getPayloadInternal()->setAction(IBB::Open); + getPayloadInternal()->setStreamID(attributes.getAttribute("sid")); + if (attributes.getAttribute("stanza") == "message") { + getPayloadInternal()->setStanzaType(IBB::MessageStanza); + } + else { + getPayloadInternal()->setStanzaType(IBB::IQStanza); + } + try { + getPayloadInternal()->setBlockSize(boost::lexical_cast<int>(attributes.getAttribute("block-size"))); + } + catch (boost::bad_lexical_cast& e) { + } + } + else if (element == "close") { + getPayloadInternal()->setAction(IBB::Close); + getPayloadInternal()->setStreamID(attributes.getAttribute("sid")); + } + } + ++level; +} + +void IBBParser::handleEndElement(const String& element, const String&) { + --level; + if (level == TopLevel) { + if (element == "data") { + std::vector<char> data; + for (size_t i = 0; i < currentText.getUTF8Size(); ++i) { + char c = currentText[i]; + if (c >= 48 && c <= 122) { + data.push_back(c); + } + } + getPayloadInternal()->setData(Base64::decode(String(&data[0], data.size()))); + } + } +} + +void IBBParser::handleCharacterData(const String& data) { + currentText += data; +} + + +} diff --git a/Swiften/Parser/PayloadParsers/IBBParser.h b/Swiften/Parser/PayloadParsers/IBBParser.h new file mode 100644 index 0000000..1fc062f --- /dev/null +++ b/Swiften/Parser/PayloadParsers/IBBParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/IBB.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class IBBParser : public GenericPayloadParser<IBB> { + public: + IBBParser(); + ~IBBParser(); + + 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, + }; + int level; + String currentText; + }; +} diff --git a/Swiften/Parser/PayloadParsers/IBBParserFactory.h b/Swiften/Parser/PayloadParsers/IBBParserFactory.h new file mode 100644 index 0000000..aa323f7 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/IBBParserFactory.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/IBBParser.h" + +namespace Swift { + class IBBParserFactory : public GenericPayloadParserFactory<IBBParser> { + public: + IBBParserFactory() : GenericPayloadParserFactory<IBBParser>("", "http://jabber.org/protocol/ibb") {} + }; +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp b/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp new file mode 100644 index 0000000..76925af --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/StreamInitiationParser.h" + +#include <boost/lexical_cast.hpp> + +#include "Swiften/Parser/PayloadParsers/FormParserFactory.h" +#include "Swiften/Parser/PayloadParsers/FormParser.h" +#include "Swiften/Base/foreach.h" + + +#define FILE_TRANSFER_NS "http://jabber.org/protocol/si/profile/file-transfer" +#define FEATURE_NEG_NS "http://jabber.org/protocol/feature-neg" + +namespace Swift { + +StreamInitiationParser::StreamInitiationParser() : level(TopLevel), formParser(0), inFile(false), inFeature(false) { + formParserFactory = new FormParserFactory(); +} + +StreamInitiationParser::~StreamInitiationParser() { + delete formParserFactory; +} + +void StreamInitiationParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + if (level == TopLevel) { + getPayloadInternal()->setID(attributes.getAttribute("id")); + if (!attributes.getAttribute("profile").isEmpty()) { + getPayloadInternal()->setIsFileTransfer(attributes.getAttribute("profile") == FILE_TRANSFER_NS); + } + } + else if (level == PayloadLevel) { + if (element == "file") { + inFile = true; + currentFile = StreamInitiation::FileInfo(); + currentFile.name = attributes.getAttribute("name"); + try { + currentFile.size = boost::lexical_cast<int>(attributes.getAttribute("size")); + } + catch (boost::bad_lexical_cast& e) { + } + } + else if (element == "feature" && ns == FEATURE_NEG_NS) { + inFeature = true; + } + } + else if (level == FileOrFeatureLevel) { + if (inFile && element == "desc") { + currentText.clear(); + } + else if (inFeature && formParserFactory->canParse(element, ns, attributes)) { + formParser = dynamic_cast<FormParser*>(formParserFactory->createPayloadParser()); + } + } + + if (formParser) { + formParser->handleStartElement(element, ns, attributes); + } + ++level; +} + +void StreamInitiationParser::handleEndElement(const String& element, const String& ns) { + --level; + if (formParser) { + formParser->handleEndElement(element, ns); + } + if (level == TopLevel) { + } + else if (level == PayloadLevel) { + if (element == "file") { + getPayloadInternal()->setFileInfo(currentFile); + inFile = false; + } + else if (element == "feature" && ns == FEATURE_NEG_NS) { + inFeature = false; + } + } + else if (level == FileOrFeatureLevel) { + if (inFile && element == "desc") { + currentFile.description = currentText; + } + else if (formParser) { + Form::ref form = formParser->getPayloadInternal(); + if (form) { + FormField::ref field = boost::dynamic_pointer_cast<FormField>(form->getField("stream-method")); + if (field) { + if (form->getType() == Form::FormType) { + foreach (const FormField::Option& option, field->getOptions()) { + getPayloadInternal()->addProvidedMethod(option.value); + } + } + else if (form->getType() == Form::SubmitType) { + if (field->getRawValues().size() > 0) { + getPayloadInternal()->setRequestedMethod(field->getRawValues()[0]); + } + } + } + } + } + } +} + +void StreamInitiationParser::handleCharacterData(const String& data) { + if (formParser) { + formParser->handleCharacterData(data); + } + else { + currentText += data; + } +} + + +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationParser.h b/Swiften/Parser/PayloadParsers/StreamInitiationParser.h new file mode 100644 index 0000000..f01567e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StreamInitiationParser.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/StreamInitiation.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class FormParserFactory; + class FormParser; + + class StreamInitiationParser : public GenericPayloadParser<StreamInitiation> { + public: + StreamInitiationParser(); + ~StreamInitiationParser(); + + 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, + FileOrFeatureLevel = 2, + FormOrDescriptionLevel = 3, + }; + int level; + FormParserFactory* formParserFactory; + FormParser* formParser; + bool inFile; + bool inFeature; + StreamInitiation::FileInfo currentFile; + String currentText; + }; +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationParserFactory.h b/Swiften/Parser/PayloadParsers/StreamInitiationParserFactory.h new file mode 100644 index 0000000..ee5ed09 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StreamInitiationParserFactory.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Parser/GenericPayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/StreamInitiationParser.h" + +namespace Swift { + class StreamInitiationParserFactory : public GenericPayloadParserFactory<StreamInitiationParser> { + public: + StreamInitiationParserFactory() : GenericPayloadParserFactory<StreamInitiationParser>("si", "http://jabber.org/protocol/si") {} + }; +} diff --git a/Swiften/Parser/PayloadParsers/UnitTest/IBBParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/IBBParserTest.cpp new file mode 100644 index 0000000..f892deb --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/IBBParserTest.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" +#include "Swiften/Elements/IBB.h" + +using namespace Swift; + +class IBBParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IBBParserTest); + CPPUNIT_TEST(testParse_Data); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParse_Data() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<data xmlns='http://jabber.org/protocol/ibb' seq='4'>\n" + "\t YWJjZGVmZ2loamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1\n" + "\t Njc4OTAK\n" + "</data>" + )); + + IBB::ref ibb = parser.getPayload<IBB>(); + CPPUNIT_ASSERT(ibb->getAction() == IBB::Data); + CPPUNIT_ASSERT_EQUAL(ByteArray("abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\x0a"), ibb->getData()); + CPPUNIT_ASSERT_EQUAL(4, ibb->getSequenceNumber()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IBBParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp new file mode 100644 index 0000000..ca8e353 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" +#include "Swiften/Elements/StreamInitiation.h" + +using namespace Swift; + +class StreamInitiationParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(StreamInitiationParserTest); + CPPUNIT_TEST(testParse_Request); + CPPUNIT_TEST(testParse_Response); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParse_Request() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<si xmlns='http://jabber.org/protocol/si' id='a0' mime-type='text/plain' profile='http://jabber.org/protocol/si/profile/file-transfer'>" + "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer' name='test.txt' size='1022'>" + "<desc>This is info about the file.</desc>" + "</file>" + "<feature xmlns='http://jabber.org/protocol/feature-neg'>" + "<x xmlns='jabber:x:data' type='form'>" + "<field var='stream-method' type='list-single'>" + "<option><value>http://jabber.org/protocol/bytestreams</value></option>" + "<option><value>jabber:iq:oob</value></option>" + "<option><value>http://jabber.org/protocol/ibb</value></option>" + "</field>" + "</x>" + "</feature>" + "</si>" + )); + + StreamInitiation::ref si = parser.getPayload<StreamInitiation>(); + CPPUNIT_ASSERT(si->getIsFileTransfer()); + CPPUNIT_ASSERT(si->getFileInfo()); + CPPUNIT_ASSERT_EQUAL(String("test.txt"), si->getFileInfo()->name); + CPPUNIT_ASSERT_EQUAL(1022, si->getFileInfo()->size); + CPPUNIT_ASSERT_EQUAL(String("This is info about the file."), si->getFileInfo()->description); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(si->getProvidedMethods().size())); + CPPUNIT_ASSERT_EQUAL(String("http://jabber.org/protocol/bytestreams"), si->getProvidedMethods()[0]); + CPPUNIT_ASSERT_EQUAL(String("jabber:iq:oob"), si->getProvidedMethods()[1]); + CPPUNIT_ASSERT_EQUAL(String("http://jabber.org/protocol/ibb"), si->getProvidedMethods()[2]); + } + + void testParse_Response() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<si xmlns='http://jabber.org/protocol/si'>" + "<feature xmlns='http://jabber.org/protocol/feature-neg'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='stream-method'>" + "<value>http://jabber.org/protocol/bytestreams</value>" + "</field>" + "</x>" + "</feature>" + "</si>" + )); + + StreamInitiation::ref si = parser.getPayload<StreamInitiation>(); + CPPUNIT_ASSERT(si->getIsFileTransfer()); + CPPUNIT_ASSERT_EQUAL(String("http://jabber.org/protocol/bytestreams"), si->getRequestedMethod()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamInitiationParserTest); diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 0256cbf..1eeb266 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -27,6 +27,7 @@ sources = [ "PayloadParsers/DiscoItemsParser.cpp", "PayloadParsers/ErrorParser.cpp", "PayloadParsers/FormParser.cpp", + "PayloadParsers/IBBParser.cpp", "PayloadParsers/CommandParser.cpp", "PayloadParsers/FullPayloadParserFactoryCollection.cpp", "PayloadParsers/PriorityParser.cpp", @@ -40,6 +41,8 @@ sources = [ "PayloadParsers/StorageParser.cpp", "PayloadParsers/StatusParser.cpp", "PayloadParsers/StatusShowParser.cpp", + "PayloadParsers/StreamInitiationParser.cpp", + "PayloadParsers/BytestreamsParser.cpp", "PayloadParsers/VCardParser.cpp", "PayloadParsers/VCardUpdateParser.cpp", "PayloadParsers/DelayParser.cpp", diff --git a/Swiften/QA/StorageTest/FileReadBytestreamTest.cpp b/Swiften/QA/StorageTest/FileReadBytestreamTest.cpp new file mode 100644 index 0000000..b8e9fd4 --- /dev/null +++ b/Swiften/QA/StorageTest/FileReadBytestreamTest.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/FileTransfer/FileReadBytestream.h" +#include "SwifTools/Application/PlatformApplicationPathProvider.h" + +using namespace Swift; + +class FileReadBytestreamTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(FileReadBytestreamTest); + CPPUNIT_TEST(testRead); + CPPUNIT_TEST(testRead_Twice); + CPPUNIT_TEST(testIsFinished_NotFinished); + CPPUNIT_TEST(testIsFinished_IsFinished); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + pathProvider = new PlatformApplicationPathProvider("FileReadBytestreamTest"); + } + + void tearDown() { + delete pathProvider; + } + + void testRead() { + std::auto_ptr<FileReadBytestream> testling(createTestling()); + + ByteArray result = testling->read(10); + + CPPUNIT_ASSERT_EQUAL(String("/*\n * Copy"), result.toString()); + } + + void testRead_Twice() { + std::auto_ptr<FileReadBytestream> testling(createTestling()); + + testling->read(10); + ByteArray result = testling->read(10); + + CPPUNIT_ASSERT_EQUAL(String("right (c) "), result.toString()); + } + + void testIsFinished_NotFinished() { + std::auto_ptr<FileReadBytestream> testling(createTestling()); + + testling->read(10); + + CPPUNIT_ASSERT(!testling->isFinished()); + } + + void testIsFinished_IsFinished() { + std::auto_ptr<FileReadBytestream> testling(createTestling()); + + testling->read(4096); + + CPPUNIT_ASSERT(testling->isFinished()); + } + + private: + FileReadBytestream* createTestling() { + return new FileReadBytestream(pathProvider->getExecutableDir() / "FileReadBytestreamTest.cpp"); + } + + PlatformApplicationPathProvider* pathProvider; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(FileReadBytestreamTest); diff --git a/Swiften/QA/StorageTest/SConscript b/Swiften/QA/StorageTest/SConscript index bb8646e..4d0a197 100644 --- a/Swiften/QA/StorageTest/SConscript +++ b/Swiften/QA/StorageTest/SConscript @@ -15,5 +15,6 @@ if env["TEST"] : tester = myenv.Program("StorageTest", [ "VCardFileStorageTest.cpp", + "FileReadBytestreamTest.cpp", ]) myenv.Test(tester, "system", is_checker = True) diff --git a/Swiften/SConscript b/Swiften/SConscript index fb79963..b371826 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -72,6 +72,7 @@ if env["SCONS_STAGE"] == "build" : "Serializer/ComponentHandshakeSerializer.cpp", "Serializer/PayloadSerializer.cpp", "Serializer/PayloadSerializerCollection.cpp", + "Serializer/PayloadSerializers/IBBSerializer.cpp", "Serializer/PayloadSerializers/CapsInfoSerializer.cpp", "Serializer/PayloadSerializers/ChatStateSerializer.cpp", "Serializer/PayloadSerializers/DiscoInfoSerializer.cpp", @@ -86,6 +87,8 @@ if env["SCONS_STAGE"] == "build" : "Serializer/PayloadSerializers/SecurityLabelSerializer.cpp", "Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.cpp", "Serializer/PayloadSerializers/SoftwareVersionSerializer.cpp", + "Serializer/PayloadSerializers/StreamInitiationSerializer.cpp", + "Serializer/PayloadSerializers/BytestreamsSerializer.cpp", "Serializer/PayloadSerializers/VCardSerializer.cpp", "Serializer/PayloadSerializers/VCardUpdateSerializer.cpp", "Serializer/PayloadSerializers/StorageSerializer.cpp", @@ -130,6 +133,7 @@ if env["SCONS_STAGE"] == "build" : "Disco", "VCards", "Network", + "FileTransfer", "History", "StreamStack", "LinkLocal", @@ -169,6 +173,8 @@ if env["SCONS_STAGE"] == "build" : File("Elements/UnitTest/StanzasTest.cpp"), File("EventLoop/UnitTest/EventLoopTest.cpp"), File("EventLoop/UnitTest/SimpleEventLoopTest.cpp"), + File("FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp"), + File("FileTransfer/UnitTest/IBBSendSessionTest.cpp"), # File("History/UnitTest/SQLiteHistoryManagerTest.cpp"), File("JID/UnitTest/JIDTest.cpp"), File("LinkLocal/UnitTest/LinkLocalConnectorTest.cpp"), @@ -186,11 +192,13 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/RosterParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/IBBParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SoftwareVersionParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/StatusParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/StatusShowParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/VCardParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/StorageParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"), @@ -230,6 +238,7 @@ if env["SCONS_STAGE"] == "build" : File("Serializer/PayloadSerializers/UnitTest/SoftwareVersionSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/StatusSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/StatusShowSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/StreamInitiationSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/VCardUpdateSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/VCardSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.cpp b/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.cpp new file mode 100644 index 0000000..9acabee --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.h" + +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" + + +namespace Swift { + +BytestreamsSerializer::BytestreamsSerializer() { +} + +String BytestreamsSerializer::serializePayload(boost::shared_ptr<Bytestreams> bytestreams) const { + XMLElement queryElement("query", "http://jabber.org/protocol/bytestreams"); + queryElement.setAttribute("sid", bytestreams->getStreamID()); + foreach(const Bytestreams::StreamHost& streamHost, bytestreams->getStreamHosts()) { + boost::shared_ptr<XMLElement> streamHostElement(new XMLElement("streamhost")); + streamHostElement->setAttribute("host", streamHost.host); + streamHostElement->setAttribute("jid", streamHost.jid.toString()); + streamHostElement->setAttribute("port", boost::lexical_cast<std::string>(streamHost.port)); + queryElement.addNode(streamHostElement); + } + + if (bytestreams->getUsedStreamHost()) { + boost::shared_ptr<XMLElement> streamHostElement(new XMLElement("streamhost-used")); + streamHostElement->setAttribute("jid", *bytestreams->getUsedStreamHost()); + queryElement.addNode(streamHostElement); + } + return queryElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.h b/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.h new file mode 100644 index 0000000..50d58c2 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/BytestreamsSerializer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/Bytestreams.h" + +namespace Swift { + class PayloadSerializerCollection; + + class BytestreamsSerializer : public GenericPayloadSerializer<Bytestreams> { + public: + BytestreamsSerializer(); + + virtual String serializePayload(boost::shared_ptr<Bytestreams>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp index ece8fd8..77c2fd4 100644 --- a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp @@ -145,7 +145,9 @@ boost::shared_ptr<XMLElement> FormSerializer::fieldToXML(boost::shared_ptr<FormF foreach (const FormField::Option& option, field->getOptions()) { boost::shared_ptr<XMLElement> optionElement(new XMLElement("option")); - optionElement->setAttribute("label", option.label); + if (!option.label.isEmpty()) { + optionElement->setAttribute("label", option.label); + } boost::shared_ptr<XMLElement> valueElement(new XMLElement("value")); valueElement->addNode(XMLTextNode::create(option.value)); diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index 8598f17..1d04456 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -7,6 +7,7 @@ #include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" #include "Swiften/Base/foreach.h" #include "Swiften/Serializer/PayloadSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/IBBSerializer.h" #include "Swiften/Serializer/PayloadSerializers/BodySerializer.h" #include "Swiften/Serializer/PayloadSerializers/SubjectSerializer.h" #include "Swiften/Serializer/PayloadSerializers/ChatStateSerializer.h" @@ -26,6 +27,8 @@ #include "Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h" #include "Swiften/Serializer/PayloadSerializers/SecurityLabelSerializer.h" #include "Swiften/Serializer/PayloadSerializers/SecurityLabelsCatalogSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/ByteStreamsSerializer.h" #include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" #include "Swiften/Serializer/PayloadSerializers/VCardUpdateSerializer.h" #include "Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h" @@ -39,6 +42,7 @@ namespace Swift { FullPayloadSerializerCollection::FullPayloadSerializerCollection() { + serializers_.push_back(new IBBSerializer()); serializers_.push_back(new BodySerializer()); serializers_.push_back(new SubjectSerializer()); serializers_.push_back(new ChatStateSerializer()); @@ -58,6 +62,8 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new StartSessionSerializer()); serializers_.push_back(new SecurityLabelSerializer()); serializers_.push_back(new SecurityLabelsCatalogSerializer()); + serializers_.push_back(new StreamInitiationSerializer()); + serializers_.push_back(new BytestreamsSerializer()); serializers_.push_back(new VCardSerializer()); serializers_.push_back(new VCardUpdateSerializer()); serializers_.push_back(new RawXMLPayloadSerializer()); diff --git a/Swiften/Serializer/PayloadSerializers/IBBSerializer.cpp b/Swiften/Serializer/PayloadSerializers/IBBSerializer.cpp new file mode 100644 index 0000000..5e52145 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/IBBSerializer.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Serializer/PayloadSerializers/IBBSerializer.h" + +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/StringCodecs/Base64.h" + +namespace Swift { + +IBBSerializer::IBBSerializer() { +} + +String IBBSerializer::serializePayload(boost::shared_ptr<IBB> ibb) const { + switch(ibb->getAction()) { + case IBB::Data: { + XMLElement ibbElement("data", "http://jabber.org/protocol/ibb"); + ibbElement.setAttribute("sid", ibb->getStreamID()); + if (ibb->getSequenceNumber() >= 0) { + ibbElement.setAttribute("seq", boost::lexical_cast<std::string>(ibb->getSequenceNumber())); + } + ibbElement.addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(Base64::encode(ibb->getData())))); + return ibbElement.serialize(); + } + case IBB::Open: { + XMLElement ibbElement("open", "http://jabber.org/protocol/ibb"); + ibbElement.setAttribute("sid", ibb->getStreamID()); + switch (ibb->getStanzaType()) { + case IBB::IQStanza: ibbElement.setAttribute("stanza", "iq"); break; + case IBB::MessageStanza: ibbElement.setAttribute("stanza", "message"); break; + } + assert(ibb->getBlockSize() > 0); + ibbElement.setAttribute("block-size", boost::lexical_cast<std::string>(ibb->getBlockSize())); + return ibbElement.serialize(); + } + case IBB::Close: { + XMLElement ibbElement("close", "http://jabber.org/protocol/ibb"); + ibbElement.setAttribute("sid", ibb->getStreamID()); + return ibbElement.serialize(); + } + } + return ""; +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/IBBSerializer.h b/Swiften/Serializer/PayloadSerializers/IBBSerializer.h new file mode 100644 index 0000000..71b1c80 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/IBBSerializer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/IBB.h" + +namespace Swift { + class PayloadSerializerCollection; + + class IBBSerializer : public GenericPayloadSerializer<IBB> { + public: + IBBSerializer(); + + virtual String serializePayload(boost::shared_ptr<IBB>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp new file mode 100644 index 0000000..71702d0 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h" + +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> + +#include "Swiften/Base/foreach.h" +#include "Swiften/Serializer/XML/XMLElement.h" +#include "Swiften/Serializer/XML/XMLTextNode.h" +#include "Swiften/Serializer/XML/XMLRawTextNode.h" +#include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FormSerializer.h" + + +#define FILE_TRANSFER_NS "http://jabber.org/protocol/si/profile/file-transfer" +#define FEATURE_NEG_NS "http://jabber.org/protocol/feature-neg" + +namespace Swift { + +StreamInitiationSerializer::StreamInitiationSerializer() { +} + +String StreamInitiationSerializer::serializePayload(boost::shared_ptr<StreamInitiation> streamInitiation) const { + assert(streamInitiation->getIsFileTransfer()); + + XMLElement siElement("si", "http://jabber.org/protocol/si"); + if (!streamInitiation->getID().isEmpty()) { + siElement.setAttribute("id", streamInitiation->getID()); + } + siElement.setAttribute("profile", FILE_TRANSFER_NS); + + if (streamInitiation->getFileInfo()) { + StreamInitiation::FileInfo file = *streamInitiation->getFileInfo(); + boost::shared_ptr<XMLElement> fileElement(new XMLElement("file", "http://jabber.org/protocol/si/profile/file-transfer")); + fileElement->setAttribute("name", file.name); + if (file.size != -1) { + fileElement->setAttribute("size", boost::lexical_cast<std::string>(file.size)); + } + if (!file.description.isEmpty()) { + boost::shared_ptr<XMLElement> descElement(new XMLElement("desc")); + descElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(file.description))); + fileElement->addNode(descElement); + } + siElement.addNode(fileElement); + } + + boost::shared_ptr<XMLElement> featureElement(new XMLElement("feature", "http://jabber.org/protocol/feature-neg")); + if (streamInitiation->getProvidedMethods().size() > 0) { + Form::ref form(new Form(Form::FormType)); + ListSingleFormField::ref field = ListSingleFormField::create(); + field->setName("stream-method"); + foreach(const String& method, streamInitiation->getProvidedMethods()) { + field->addOption(FormField::Option("", method)); + } + form->addField(field); + featureElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(FormSerializer().serialize(form)))); + } + else if (!streamInitiation->getRequestedMethod().isEmpty()) { + Form::ref form(new Form(Form::SubmitType)); + ListSingleFormField::ref field = ListSingleFormField::create(streamInitiation->getRequestedMethod()); + field->setName("stream-method"); + form->addField(field); + featureElement->addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(FormSerializer().serialize(form)))); + } + siElement.addNode(featureElement); + return siElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h new file mode 100644 index 0000000..35c71b9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Serializer/GenericPayloadSerializer.h" +#include "Swiften/Elements/StreamInitiation.h" + +namespace Swift { + class PayloadSerializerCollection; + + class StreamInitiationSerializer : public GenericPayloadSerializer<StreamInitiation> { + public: + StreamInitiationSerializer(); + + virtual String serializePayload(boost::shared_ptr<StreamInitiation>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp index ebc8664..4ed3ba9 100644 --- a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp @@ -84,7 +84,7 @@ class FormSerializerTest : public CppUnit::TestFixture { field->addOption(FormField::Option("30", "30")); field->addOption(FormField::Option("50", "50")); field->addOption(FormField::Option("100", "100")); - field->addOption(FormField::Option("None", "none")); + field->addOption(FormField::Option("", "none")); form->addField(field); std::vector<JID> jids; @@ -132,7 +132,7 @@ class FormSerializerTest : public CppUnit::TestFixture { "<option label=\"30\"><value>30</value></option>" "<option label=\"50\"><value>50</value></option>" "<option label=\"100\"><value>100</value></option>" - "<option label=\"None\"><value>none</value></option>" + "<option><value>none</value></option>" "</field>" "<field label=\"People to invite\" type=\"jid-multi\" var=\"invitelist\">" "<desc>Tell all your friends about your new bot!</desc>" diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/StreamInitiationSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/StreamInitiationSerializerTest.cpp new file mode 100644 index 0000000..12c8485 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/StreamInitiationSerializerTest.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.h" + +using namespace Swift; + +class StreamInitiationSerializerTest : public CppUnit::TestFixture{ + CPPUNIT_TEST_SUITE(StreamInitiationSerializerTest); + CPPUNIT_TEST(testSerialize_Request); + CPPUNIT_TEST(testSerialize_Response); + CPPUNIT_TEST_SUITE_END(); + + public: + void testSerialize_Request() { + StreamInitiationSerializer testling; + boost::shared_ptr<StreamInitiation> streamInitiation(new StreamInitiation()); + StreamInitiation::FileInfo fileInfo("test.txt", "This is info about the file.", 1022); + streamInitiation->setID("a0"); + streamInitiation->setFileInfo(fileInfo); + streamInitiation->addProvidedMethod("http://jabber.org/protocol/bytestreams"); + streamInitiation->addProvidedMethod("jabber:iq:oob"); + streamInitiation->addProvidedMethod("http://jabber.org/protocol/ibb"); + + CPPUNIT_ASSERT_EQUAL(String( + "<si id=\"a0\" profile=\"http://jabber.org/protocol/si/profile/file-transfer\" xmlns=\"http://jabber.org/protocol/si\">" + "<file name=\"test.txt\" size=\"1022\" xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<desc>This is info about the file.</desc>" + "</file>" + "<feature xmlns=\"http://jabber.org/protocol/feature-neg\">" + "<x type=\"form\" xmlns=\"jabber:x:data\">" + "<field type=\"list-single\" var=\"stream-method\">" + "<option><value>http://jabber.org/protocol/bytestreams</value></option>" + "<option><value>jabber:iq:oob</value></option>" + "<option><value>http://jabber.org/protocol/ibb</value></option>" + "</field>" + "</x>" + "</feature>" + "</si>" + ), testling.serialize(streamInitiation)); + } + + void testSerialize_Response() { + StreamInitiationSerializer testling; + boost::shared_ptr<StreamInitiation> streamInitiation(new StreamInitiation()); + streamInitiation->setRequestedMethod("http://jabber.org/protocol/bytestreams"); + + CPPUNIT_ASSERT_EQUAL(String( + "<si profile=\"http://jabber.org/protocol/si/profile/file-transfer\" xmlns=\"http://jabber.org/protocol/si\">" + "<feature xmlns=\"http://jabber.org/protocol/feature-neg\">" + "<x type=\"submit\" xmlns=\"jabber:x:data\">" + "<field type=\"list-single\" var=\"stream-method\">" + "<value>http://jabber.org/protocol/bytestreams</value>" + "</field>" + "</x>" + "</feature>" + "</si>" + ), testling.serialize(streamInitiation)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(StreamInitiationSerializerTest); |