diff options
-rw-r--r-- | Swiften/Network/BOSHConnection.cpp | 3 | ||||
-rw-r--r-- | Swiften/Network/UnitTest/BOSHConnectionTest.cpp | 44 |
2 files changed, 45 insertions, 2 deletions
diff --git a/Swiften/Network/BOSHConnection.cpp b/Swiften/Network/BOSHConnection.cpp index 1c468f1..b4ffa7d 100644 --- a/Swiften/Network/BOSHConnection.cpp +++ b/Swiften/Network/BOSHConnection.cpp @@ -1,38 +1,38 @@ /* * Copyright (c) 2011 Thilo Cestonaro * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ /* - * Copyright (c) 2011-2016 Isode Limited. + * Copyright (c) 2011-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Network/BOSHConnection.h> #include <string> #include <thread> #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #include <Swiften/Base/ByteArray.h> #include <Swiften/Base/Concat.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> #include <Swiften/Network/HostAddressPort.h> #include <Swiften/Parser/BOSHBodyExtractor.h> #include <Swiften/StreamStack/DummyStreamLayer.h> #include <Swiften/StreamStack/TLSLayer.h> #include <Swiften/TLS/TLSContext.h> #include <Swiften/TLS/TLSOptions.h> namespace Swift { BOSHConnection::BOSHConnection(const URL& boshURL, Connector::ref connector, XMLParserFactory* parserFactory, TLSContextFactory* tlsContextFactory, const TLSOptions& tlsOptions) : boshURL_(boshURL), connector_(connector), parserFactory_(parserFactory), sid_(), @@ -269,60 +269,61 @@ void BOSHConnection::startStream(const std::string& to, unsigned long long rid) << "Content-Length: " << contentString.size() << "\r\n\r\n" << contentString; waitingForStartResponse_ = true; SafeByteArray safeHeader = createSafeByteArray(header.str()); onBOSHDataWritten(safeHeader); writeData(safeHeader); SWIFT_LOG(debug) << "write stream header: " << safeByteArrayToString(safeHeader) << std::endl; } void BOSHConnection::handleDataRead(std::shared_ptr<SafeByteArray> data) { onBOSHDataRead(*data); buffer_ = concat(buffer_, *data); std::string response = safeByteArrayToString(buffer_); if (response.find("\r\n\r\n") == std::string::npos) { onBOSHDataRead(createSafeByteArray("[[Previous read incomplete, pending]]")); return; } std::string httpCode = response.substr(response.find(" ") + 1, 3); if (httpCode != "200") { onHTTPError(httpCode); return; } BOSHBodyExtractor parser(parserFactory_, createByteArray(response.substr(response.find("\r\n\r\n") + 4))); if (parser.getBody()) { if (parser.getBody()->attributes.getAttribute("type") == "terminate") { BOSHError::Type errorType = parseTerminationCondition(parser.getBody()->attributes.getAttribute("condition")); onSessionTerminated(errorType == BOSHError::NoError ? std::shared_ptr<BOSHError>() : std::make_shared<BOSHError>(errorType)); + return; } buffer_.clear(); if (waitingForStartResponse_) { waitingForStartResponse_ = false; sid_ = parser.getBody()->attributes.getAttribute("sid"); std::string requestsString = parser.getBody()->attributes.getAttribute("requests"); size_t requests = 2; if (!requestsString.empty()) { try { requests = boost::lexical_cast<size_t>(requestsString); } catch (const boost::bad_lexical_cast&) { } } onSessionStarted(sid_, requests); } SafeByteArray payload = createSafeByteArray(parser.getBody()->content); /* Say we're good to go again, so don't add anything after here in the method */ pending_ = false; onXMPPDataRead(payload); } } BOSHError::Type BOSHConnection::parseTerminationCondition(const std::string& text) { BOSHError::Type condition = BOSHError::UndefinedCondition; if (text == "bad-request") { condition = BOSHError::BadRequest; } else if (text == "host-gone") { diff --git a/Swiften/Network/UnitTest/BOSHConnectionTest.cpp b/Swiften/Network/UnitTest/BOSHConnectionTest.cpp index 99dd462..e34cb96 100644 --- a/Swiften/Network/UnitTest/BOSHConnectionTest.cpp +++ b/Swiften/Network/UnitTest/BOSHConnectionTest.cpp @@ -1,84 +1,87 @@ /* - * Copyright (c) 2011-2016 Isode Limited. + * Copyright (c) 2011-2017 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <memory> #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #include <boost/optional.hpp> #include <QA/Checker/IO.h> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/EventLoop/DummyEventLoop.h> #include <Swiften/Network/BOSHConnection.h> #include <Swiften/Network/Connection.h> #include <Swiften/Network/ConnectionFactory.h> #include <Swiften/Network/DummyTimerFactory.h> #include <Swiften/Network/HostAddressPort.h> #include <Swiften/Network/StaticDomainNameResolver.h> #include <Swiften/Parser/PlatformXMLParserFactory.h> #include <Swiften/TLS/TLSOptions.h> using namespace Swift; class BOSHConnectionTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(BOSHConnectionTest); CPPUNIT_TEST(testHeader); CPPUNIT_TEST(testReadiness_ok); CPPUNIT_TEST(testReadiness_pending); CPPUNIT_TEST(testReadiness_disconnect); CPPUNIT_TEST(testReadiness_noSID); CPPUNIT_TEST(testWrite_Receive); CPPUNIT_TEST(testWrite_ReceiveTwice); CPPUNIT_TEST(testRead_Fragment); CPPUNIT_TEST(testHTTPRequest); CPPUNIT_TEST(testHTTPRequest_Empty); + CPPUNIT_TEST(testTerminate); + CPPUNIT_TEST(testTerminateWithAdditionalData); CPPUNIT_TEST_SUITE_END(); public: void setUp() { eventLoop = new DummyEventLoop(); connectionFactory = new MockConnectionFactory(eventLoop); resolver = new StaticDomainNameResolver(eventLoop); timerFactory = new DummyTimerFactory(); tlsContextFactory = nullptr; connectFinished = false; disconnected = false; disconnectedError = false; + sessionTerminatedError.reset(); dataRead.clear(); } void tearDown() { eventLoop->processEvents(); delete connectionFactory; delete resolver; delete timerFactory; delete eventLoop; } void testHeader() { BOSHConnection::ref testling = createTestling(); testling->connect(); eventLoop->processEvents(); testling->startStream("wonderland.lit", 1); std::string initial("<body wait='60' " "inactivity='30' " "polling='5' " "requests='2' " "hold='1' " "maxpause='120' " "sid='MyShinySID' " "ver='1.6' " "from='wonderland.lit' " "xmlns='http://jabber.org/protocol/httpbind'/>"); readResponse(initial, connectionFactory->connections[0]); CPPUNIT_ASSERT_EQUAL(std::string("MyShinySID"), sid); CPPUNIT_ASSERT(testling->isReadyToSend()); } @@ -163,92 +166,130 @@ class BOSHConnectionTest : public CppUnit::TestFixture { "<bl")); std::shared_ptr<SafeByteArray> data3 = std::make_shared<SafeByteArray>(createSafeByteArray( "ah/>" "</body>")); connection->onDataRead(data1); connection->onDataRead(data2); CPPUNIT_ASSERT(dataRead.empty()); connection->onDataRead(data3); CPPUNIT_ASSERT_EQUAL(std::string("<blah/>"), byteArrayToString(dataRead)); } void testHTTPRequest() { std::string data = "<blah/>"; std::string sid = "wigglebloom"; std::string fullBody = "<body xmlns='http://jabber.org/protocol/httpbind' sid='" + sid + "' rid='20'>" + data + "</body>"; std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 20, sid, URL()); CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second); } void testHTTPRequest_Empty() { std::string data = ""; std::string sid = "wigglebloomsickle"; std::string fullBody = "<body rid='42' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'>" + data + "</body>"; std::pair<SafeByteArray, size_t> http = BOSHConnection::createHTTPRequest(createSafeByteArray(data), false, false, 42, sid, URL()); CPPUNIT_ASSERT_EQUAL(fullBody.size(), http.second); std::string response = safeByteArrayToString(http.first); size_t bodyPosition = response.find("\r\n\r\n"); CPPUNIT_ASSERT_EQUAL(fullBody, response.substr(bodyPosition+4)); } + void testTerminate() { + BOSHConnection::ref testling = createTestling(); + testling->connect(); + eventLoop->processEvents(); + testling->startStream("localhost", 1); + std::string initial("<body xmlns=\"http://jabber.org/protocol/httpbind\" " + "condition=\"bad-request\" " + "type=\"terminate\">" + "</body>"); + readResponse(initial, connectionFactory->connections[0]); + CPPUNIT_ASSERT(sessionTerminatedError); + CPPUNIT_ASSERT_EQUAL(BOSHError::BadRequest, sessionTerminatedError->getType()); + CPPUNIT_ASSERT_EQUAL(true, dataRead.empty()); + } + + // On a BOSH error no additional data may be emitted. + void testTerminateWithAdditionalData() { + BOSHConnection::ref testling = createTestling(); + testling->connect(); + eventLoop->processEvents(); + testling->startStream("localhost", 1); + std::string initial("<body xmlns=\"http://jabber.org/protocol/httpbind\" " + "condition=\"bad-request\" " + "type=\"terminate\">" + "<text>an error message</text>" + "</body>"); + readResponse(initial, connectionFactory->connections[0]); + CPPUNIT_ASSERT(sessionTerminatedError); + CPPUNIT_ASSERT_EQUAL(BOSHError::BadRequest, sessionTerminatedError->getType()); + CPPUNIT_ASSERT_EQUAL(true, dataRead.empty()); + } + + private: BOSHConnection::ref createTestling() { resolver->addAddress("wonderland.lit", HostAddress::fromString("127.0.0.1").get()); Connector::ref connector = Connector::create("wonderland.lit", 5280, boost::optional<std::string>(), resolver, connectionFactory, timerFactory); BOSHConnection::ref c = BOSHConnection::create(URL("http", "wonderland.lit", 5280, "/http-bind"), connector, &parserFactory, tlsContextFactory, TLSOptions()); c->onConnectFinished.connect(boost::bind(&BOSHConnectionTest::handleConnectFinished, this, _1)); c->onDisconnected.connect(boost::bind(&BOSHConnectionTest::handleDisconnected, this, _1)); c->onXMPPDataRead.connect(boost::bind(&BOSHConnectionTest::handleDataRead, this, _1)); c->onSessionStarted.connect(boost::bind(&BOSHConnectionTest::handleSID, this, _1)); + c->onSessionTerminated.connect(boost::bind(&BOSHConnectionTest::handleSessionTerminated, this, _1)); c->setRID(42); return c; } void handleConnectFinished(bool error) { connectFinished = true; connectFinishedWithError = error; } void handleDisconnected(bool e) { disconnected = true; disconnectedError = e; } void handleDataRead(const SafeByteArray& d) { append(dataRead, d); } void handleSID(const std::string& s) { sid = s; } + void handleSessionTerminated(BOSHError::ref error) { + sessionTerminatedError = error; + } + struct MockConnection : public Connection { public: MockConnection(const std::vector<HostAddressPort>& failingPorts, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), disconnected(false) { } void listen() { assert(false); } void connect(const HostAddressPort& address) { hostAddressPort = address; bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end(); eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail)); } HostAddressPort getLocalAddress() const { return HostAddressPort(); } HostAddressPort getRemoteAddress() const { return HostAddressPort(); } void disconnect() { disconnected = true; onDisconnected(boost::optional<Connection::Error>()); } void write(const SafeByteArray& d) { append(dataWritten, d); } EventLoop* eventLoop; boost::optional<HostAddressPort> hostAddressPort; std::vector<HostAddressPort> failingPorts; ByteArray dataWritten; bool disconnected; @@ -266,41 +307,42 @@ class BOSHConnectionTest : public CppUnit::TestFixture { EventLoop* eventLoop; std::vector< std::shared_ptr<MockConnection> > connections; std::vector<HostAddressPort> failingPorts; }; void readResponse(const std::string& response, std::shared_ptr<MockConnection> connection) { std::shared_ptr<SafeByteArray> data1 = std::make_shared<SafeByteArray>(createSafeByteArray( "HTTP/1.1 200 OK\r\n" "Content-Type: text/xml; charset=utf-8\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Headers: Content-Type\r\n" "Content-Length: ")); connection->onDataRead(data1); std::shared_ptr<SafeByteArray> data2 = std::make_shared<SafeByteArray>(createSafeByteArray(boost::lexical_cast<std::string>(response.size()))); connection->onDataRead(data2); std::shared_ptr<SafeByteArray> data3 = std::make_shared<SafeByteArray>(createSafeByteArray("\r\n\r\n")); connection->onDataRead(data3); std::shared_ptr<SafeByteArray> data4 = std::make_shared<SafeByteArray>(createSafeByteArray(response)); connection->onDataRead(data4); } private: DummyEventLoop* eventLoop; MockConnectionFactory* connectionFactory; bool connectFinished; bool connectFinishedWithError; bool disconnected; bool disconnectedError; + BOSHError::ref sessionTerminatedError; ByteArray dataRead; PlatformXMLParserFactory parserFactory; StaticDomainNameResolver* resolver; TimerFactory* timerFactory; TLSContextFactory* tlsContextFactory; std::string sid; }; CPPUNIT_TEST_SUITE_REGISTRATION(BOSHConnectionTest); |