diff options
Diffstat (limited to 'Swiften/Network/BOSHConnection.cpp')
-rw-r--r-- | Swiften/Network/BOSHConnection.cpp | 340 |
1 files changed, 246 insertions, 94 deletions
diff --git a/Swiften/Network/BOSHConnection.cpp b/Swiften/Network/BOSHConnection.cpp index 549c652..09548e9 100644 --- a/Swiften/Network/BOSHConnection.cpp +++ b/Swiften/Network/BOSHConnection.cpp @@ -4,129 +4,281 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "BOSHConnection.h" +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Network/BOSHConnection.h> + #include <boost/bind.hpp> #include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> #include <string> #include <Swiften/Network/ConnectionFactory.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> +#include <Swiften/Base/Concat.h> #include <Swiften/Base/ByteArray.h> #include <Swiften/Network/HostAddressPort.h> -#include <Swiften/Parser/BOSHParser.h> +#include <Swiften/Network/TLSConnection.h> +#include <Swiften/Parser/BOSHBodyExtractor.h> namespace Swift { - BOSHConnection::BOSHConnection(ConnectionFactory* connectionFactory) - : connectionFactory_(connectionFactory), server_(HostAddressPort(HostAddress("0.0.0.0"), 0)), sid_() - { - reopenAfterAction = true; - } +BOSHConnection::BOSHConnection(const URL& boshURL, ConnectionFactory* connectionFactory, XMLParserFactory* parserFactory, TLSContextFactory* tlsFactory) + : boshURL_(boshURL), + connectionFactory_(connectionFactory), + parserFactory_(parserFactory), + sid_(), + waitingForStartResponse_(false), + pending_(false), + tlsFactory_(tlsFactory), + connectionReady_(false) +{ +} - BOSHConnection::~BOSHConnection() { - if (newConnection_) { - newConnection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1)); - newConnection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1)); - } - if (currentConnection_) { - currentConnection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1)); - currentConnection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1)); - } +BOSHConnection::~BOSHConnection() { + if (connection_) { + connection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1)); + connection_->onDataRead.disconnect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1)); + connection_->onDisconnected.disconnect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1)); } + disconnect(); +} - void BOSHConnection::connect(const HostAddressPort& server) { - server_ = server; - newConnection_ = connectionFactory_->createConnection(); - newConnection_->onConnectFinished.connect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1)); - newConnection_->onDataRead.connect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1)); - newConnection_->onDisconnected.connect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1)); - SWIFT_LOG(debug) << "connect to server " << server.getAddress().toString() << ":" << server.getPort() << std::endl; - newConnection_->connect(HostAddressPort(HostAddress("85.10.192.88"), 5280)); - } +void BOSHConnection::connect(const HostAddressPort& server) { + /* FIXME: Redundant parameter */ + Connection::ref rawConnection = connectionFactory_->createConnection(); + connection_ = (boshURL_.getScheme() == "https") ? boost::make_shared<TLSConnection>(rawConnection, tlsFactory_) : rawConnection; + connection_->onConnectFinished.connect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1)); + connection_->onDataRead.connect(boost::bind(&BOSHConnection::handleDataRead, shared_from_this(), _1)); + connection_->onDisconnected.connect(boost::bind(&BOSHConnection::handleDisconnected, shared_from_this(), _1)); + connection_->connect(HostAddressPort(HostAddress(boshURL_.getHost()), boshURL_.getPort())); +} - void BOSHConnection::listen() { - assert(false); +void BOSHConnection::listen() { + assert(false); +} + +void BOSHConnection::disconnect() { + if(connection_) { + connection_->disconnect(); + sid_ = ""; } +} - void BOSHConnection::disconnect() { - if(newConnection_) - newConnection_->disconnect(); +void BOSHConnection::restartStream() { + write(createSafeByteArray(""), true, false); +} + +void BOSHConnection::terminateStream() { + write(createSafeByteArray(""), false, true); +} - if(currentConnection_) - currentConnection_->disconnect(); - } - void BOSHConnection::write(const SafeByteArray& data) { - SWIFT_LOG(debug) << "write data: " << safeByteArrayToString(data) << std::endl; +void BOSHConnection::write(const SafeByteArray& data) { + write(data, false, false); +} + +std::pair<SafeByteArray, size_t> BOSHConnection::createHTTPRequest(const SafeByteArray& data, bool streamRestart, bool terminate, long rid, const std::string& sid, const URL& boshURL) { + size_t size; + std::stringstream content; + SafeByteArray contentTail = createSafeByteArray("</body>"); + std::stringstream header; + + content << "<body rid='" << rid << "' sid='" << sid << "'"; + if (streamRestart) { + content << " xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'"; } + if (terminate) { + content << " type='terminate'"; + } + content << " xmlns='http://jabber.org/protocol/httpbind'>"; - void BOSHConnection::handleConnectionConnectFinished(bool error) { - newConnection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1)); - if(error) { - onConnectFinished(true); - return; - } + SafeByteArray safeContent = createSafeByteArray(content.str()); + safeContent.insert(safeContent.end(), data.begin(), data.end()); + safeContent.insert(safeContent.end(), contentTail.begin(), contentTail.end()); - if(sid_.size() == 0) { - // Session Creation Request - std::stringstream content; - std::stringstream header; - - content << "<body content='text/xml; charset=utf-8'" - << " from='ephraim@0x10.de'" - << " hold='1'" - << " to='0x10.de'" - << " ver='1.6'" - << " wait='60'" - << " ack='1'" - << " xml:lang='en'" - << " xmlns='http://jabber.org/protocol/httpbind' />\r\n"; - - header << "POST /http-bind HTTP/1.1\r\n" - << "Host: 0x10.de:5280\r\n" - << "Accept-Encoding: deflate\r\n" - << "Content-Type: text/xml; charset=utf-8\r\n" - << "Content-Length: " << content.str().size() << "\r\n\r\n" - << content.str(); - - SWIFT_LOG(debug) << "request: "; - newConnection_->write(createSafeByteArray(header.str())); - } + size = safeContent.size(); + + header << "POST /" << boshURL.getPath() << " HTTP/1.1\r\n" + << "Host: " << boshURL.getHost() << ":" << boshURL.getPort() << "\r\n" + /*<< "Accept-Encoding: deflate\r\n"*/ + << "Content-Type: text/xml; charset=utf-8\r\n" + << "Content-Length: " << size << "\r\n\r\n"; + + SafeByteArray safeHeader = createSafeByteArray(header.str()); + safeHeader.insert(safeHeader.end(), safeContent.begin(), safeContent.end()); + + return std::pair<SafeByteArray, size_t>(safeHeader, size); +} + +void BOSHConnection::write(const SafeByteArray& data, bool streamRestart, bool terminate) { + assert(connectionReady_); + assert(!sid_.empty()); + + SafeByteArray safeHeader = createHTTPRequest(data, streamRestart, terminate, rid_, sid_, boshURL_).first; + + onBOSHDataWritten(safeHeader); + connection_->write(safeHeader); + pending_ = true; + + SWIFT_LOG(debug) << "write data: " << safeByteArrayToString(safeHeader) << std::endl; +} + +void BOSHConnection::handleConnectionConnectFinished(bool error) { + connection_->onConnectFinished.disconnect(boost::bind(&BOSHConnection::handleConnectionConnectFinished, shared_from_this(), _1)); + connectionReady_ = !error; + onConnectFinished(error); +} + +void BOSHConnection::startStream(const std::string& to, unsigned long rid) { + assert(connectionReady_); + // Session Creation Request + std::stringstream content; + std::stringstream header; + + content << "<body content='text/xml; charset=utf-8'" + << " hold='1'" + << " to='" << to << "'" + << " rid='" << rid << "'" + << " ver='1.6'" + << " wait='60'" /* FIXME: we probably want this configurable*/ + /*<< " ack='0'" FIXME: support acks */ + << " xml:lang='en'" + << " xmlns:xmpp='urn:xmpp:bosh'" + << " xmpp:version='1.0'" + << " xmlns='http://jabber.org/protocol/httpbind' />"; + + std::string contentString = content.str(); + + header << "POST /" << boshURL_.getPath() << " HTTP/1.1\r\n" + << "Host: " << boshURL_.getHost() << ":" << boshURL_.getPort() << "\r\n" + /*<< "Accept-Encoding: deflate\r\n"*/ + << "Content-Type: text/xml; charset=utf-8\r\n" + << "Content-Length: " << contentString.size() << "\r\n\r\n" + << contentString; + + waitingForStartResponse_ = true; + SafeByteArray safeHeader = createSafeByteArray(header.str()); + onBOSHDataWritten(safeHeader); + connection_->write(safeHeader); + SWIFT_LOG(debug) << "write stream header: " << safeByteArrayToString(safeHeader) << std::endl; +} + +void BOSHConnection::handleDataRead(boost::shared_ptr<SafeByteArray> data) { + onBOSHDataRead(*data.get()); + buffer_ = concat(buffer_, *data.get()); + 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; } - void BOSHConnection::handleDataRead(const SafeByteArray& data) { - std::string response = safeByteArrayToString(data); - assert(response.find("\r\n\r\n") != std::string::npos); - - SWIFT_LOG(debug) << "response: " << response.substr(response.find("\r\n\r\n") + 4) << std::endl; - - BOSHParser parser; - if(parser.parse(response.substr(response.find("\r\n\r\n") + 4))) { - sid_ = parser.getAttribute("sid"); - onConnectFinished(false); - int bodyStartElementLength = 0; - bool inQuote = false; - for(size_t i= 0; i < response.size(); i++) { - if(response.c_str()[i] == '\'' || response.c_str()[i] == '"') { - inQuote = !inQuote; - } - else if(!inQuote && response.c_str()[i] == '>') { - bodyStartElementLength = i + 1; - break; - } + 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 ? boost::shared_ptr<BOSHError>() : boost::make_shared<BOSHError>(errorType)); + } + buffer_.clear(); + if (waitingForStartResponse_) { + waitingForStartResponse_ = false; + sid_ = (*parser.getBody()).attributes.getAttribute("sid"); + std::string requestsString = (*parser.getBody()).attributes.getAttribute("requests"); + int requests = 2; + if (!requestsString.empty()) { + requests = boost::lexical_cast<size_t>(requestsString); } - SafeByteArray payload = createSafeByteArray(response.substr(bodyStartElementLength, response.size() - bodyStartElementLength - 7)); - SWIFT_LOG(debug) << "payload: " << safeByteArrayToString(payload) << std::endl; - onDataRead(payload); + 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); } - void BOSHConnection::handleDisconnected(const boost::optional<Error>& error) { - onDisconnected(error); - } +} - HostAddressPort BOSHConnection::getLocalAddress() const { - return newConnection_->getLocalAddress(); +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") { + condition = BOSHError::HostGone; + } + else if (text == "host-unknown") { + condition = BOSHError::HostUnknown; + } + else if (text == "improper-addressing") { + condition = BOSHError::ImproperAddressing; + } + else if (text == "internal-server-error") { + condition = BOSHError::InternalServerError; + } + else if (text == "item-not-found") { + condition = BOSHError::ItemNotFound; + } + else if (text == "other-request") { + condition = BOSHError::OtherRequest; + } + else if (text == "policy-violation") { + condition = BOSHError::PolicyViolation; + } + else if (text == "remote-connection-failed") { + condition = BOSHError::RemoteConnectionFailed; + } + else if (text == "remote-stream-error") { + condition = BOSHError::RemoteStreamError; } + else if (text == "see-other-uri") { + condition = BOSHError::SeeOtherURI; + } + else if (text == "system-shutdown") { + condition = BOSHError::SystemShutdown; + } + else if (text == "") { + condition = BOSHError::NoError; + } + return condition; +} + +const std::string& BOSHConnection::getSID() { + return sid_; +} + +void BOSHConnection::setRID(unsigned long rid) { + rid_ = rid; +} + +void BOSHConnection::setSID(const std::string& sid) { + sid_ = sid; +} + +void BOSHConnection::handleDisconnected(const boost::optional<Error>& error) { + onDisconnected(error); + sid_ = ""; + connectionReady_ = false; +} + +HostAddressPort BOSHConnection::getLocalAddress() const { + return connection_->getLocalAddress(); +} + +bool BOSHConnection::isReadyToSend() { + /* Without pipelining you need to not send more without first receiving the response */ + /* With pipelining you can. Assuming we can't, here */ + return connectionReady_ && !pending_ && !waitingForStartResponse_ && !sid_.empty(); +} + } |