diff options
author | Kevin Smith <git@kismith.co.uk> | 2011-11-12 16:56:21 (GMT) |
---|---|---|
committer | Kevin Smith <git@kismith.co.uk> | 2011-12-13 08:17:58 (GMT) |
commit | 81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d (patch) | |
tree | 4371c5808ee26b2b5ed79ace9ccb439ff2988945 /Swiften/Client | |
parent | fd17fe0d239f97cedebe4ceffa234155bd299b68 (diff) | |
download | swift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.zip swift-contrib-81c09a0f6a3e87b078340d7f35d0dea4c03f3a6d.tar.bz2 |
BOSH Support for Swiften
This adds support for BOSH to Swiften. It does not expose it to Swift.
Release-Notes: Swiften now allows connects over BOSH, if used appropriately.
Diffstat (limited to 'Swiften/Client')
-rw-r--r-- | Swiften/Client/ClientOptions.h | 28 | ||||
-rw-r--r-- | Swiften/Client/ClientSession.cpp | 2 | ||||
-rw-r--r-- | Swiften/Client/ClientXMLTracer.cpp | 17 | ||||
-rw-r--r-- | Swiften/Client/ClientXMLTracer.h | 3 | ||||
-rw-r--r-- | Swiften/Client/CoreClient.cpp | 82 | ||||
-rw-r--r-- | Swiften/Client/CoreClient.h | 5 | ||||
-rw-r--r-- | Swiften/Client/UnitTest/ClientSessionTest.cpp | 4 |
7 files changed, 105 insertions, 36 deletions
diff --git a/Swiften/Client/ClientOptions.h b/Swiften/Client/ClientOptions.h index 3b51a87..06bf947 100644 --- a/Swiften/Client/ClientOptions.h +++ b/Swiften/Client/ClientOptions.h @@ -1,26 +1,29 @@ /* * Copyright (c) 2011 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once +#include <Swiften/Base/URL.h> +#include <Swiften/Base/SafeString.h> + namespace Swift { struct ClientOptions { enum UseTLS { NeverUseTLS, UseTLSWhenAvailable, RequireTLS }; - ClientOptions() : useStreamCompression(true), useTLS(UseTLSWhenAvailable), allowPLAINWithoutTLS(false), useStreamResumption(false), forgetPassword(false), useAcks(true) { + ClientOptions() : useStreamCompression(true), useTLS(UseTLSWhenAvailable), allowPLAINWithoutTLS(false), useStreamResumption(false), forgetPassword(false), useAcks(true), boshHTTPConnectProxyAuthID(""), boshHTTPConnectProxyAuthPassword("") { } /** * Whether ZLib stream compression should be used when available. * * Default: true */ bool useStreamCompression; @@ -55,11 +58,34 @@ namespace Swift { * Default: false */ bool forgetPassword; /** * Use XEP-0198 acks in the stream when available. * Default: true */ bool useAcks; + + /** + * If non-empty, use BOSH instead of direct TCP, with the given URL. + * The host currently needs to be specified by IP, rather than hostname. + * Default: empty (no BOSH) + */ + URL boshURL; + + /** + * If non-empty, BOSH connections will try to connect over this HTTP CONNECT + * proxy instead of directly. + * Must be specified by IP, rather than hostname. + * Default: empty (no proxy) + */ + URL boshHTTPConnectProxyURL; + + /** + * If this and matching Password are non-empty, BOSH connections over + * HTTP CONNECT proxies will use these credentials for proxy access. + * Default: empty (no authentication needed by the proxy) + */ + SafeString boshHTTPConnectProxyAuthID; + SafeString boshHTTPConnectProxyAuthPassword; }; } diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp index 55e0bc2..bfc9313 100644 --- a/Swiften/Client/ClientSession.cpp +++ b/Swiften/Client/ClientSession.cpp @@ -175,19 +175,19 @@ void ClientSession::handleElement(boost::shared_ptr<Element> element) { } if (streamFeatures->hasStartTLS() && stream->supportsTLSEncryption() && useTLS != NeverUseTLS) { state = WaitingForEncrypt; stream->writeElement(boost::make_shared<StartTLSRequest>()); } else if (useTLS == RequireTLS && !stream->isTLSEncrypted()) { finishSession(Error::NoSupportedAuthMechanismsError); } - else if (useStreamCompression && streamFeatures->hasCompressionMethod("zlib")) { + else if (useStreamCompression && stream->supportsZLibCompression() && streamFeatures->hasCompressionMethod("zlib")) { state = Compressing; stream->writeElement(boost::make_shared<CompressRequest>("zlib")); } else if (streamFeatures->hasAuthenticationMechanisms()) { if (stream->hasTLSCertificate()) { if (streamFeatures->hasAuthenticationMechanism("EXTERNAL")) { state = Authenticating; stream->writeElement(boost::make_shared<AuthRequest>("EXTERNAL", createSafeByteArray(""))); } diff --git a/Swiften/Client/ClientXMLTracer.cpp b/Swiften/Client/ClientXMLTracer.cpp index c1093eb..405e3d1 100644 --- a/Swiften/Client/ClientXMLTracer.cpp +++ b/Swiften/Client/ClientXMLTracer.cpp @@ -5,31 +5,44 @@ */ #include <Swiften/Client/ClientXMLTracer.h> #include <iostream> #include <boost/bind.hpp> namespace Swift { -ClientXMLTracer::ClientXMLTracer(CoreClient* client) { +ClientXMLTracer::ClientXMLTracer(CoreClient* client, bool bosh) : bosh(bosh) { beautifier = new XMLBeautifier(true, true); client->onDataRead.connect(boost::bind(&ClientXMLTracer::printData, this, '<', _1)); client->onDataWritten.connect(boost::bind(&ClientXMLTracer::printData, this, '>', _1)); } ClientXMLTracer::~ClientXMLTracer() { delete beautifier; } void ClientXMLTracer::printData(char direction, const SafeByteArray& data) { printLine(direction); - std::cerr << beautifier->beautify(byteArrayToString(ByteArray(data.begin(), data.end()))) << std::endl; + if (bosh) { + std::string line = byteArrayToString(ByteArray(data.begin(), data.end())); + size_t endOfHTTP = line.find("\r\n\r\n"); + if (false && endOfHTTP != std::string::npos) { + /* Disabled because it swallows bits of XML (namespaces, if I recall) */ + std::cerr << line.substr(0, endOfHTTP) << std::endl << beautifier->beautify(line.substr(endOfHTTP)) << std::endl; + } + else { + std::cerr << line << std::endl; + } + } + else { + std::cerr << beautifier->beautify(byteArrayToString(ByteArray(data.begin(), data.end()))) << std::endl; + } } void ClientXMLTracer::printLine(char c) { for (unsigned int i = 0; i < 80; ++i) { std::cerr << c; } std::cerr << std::endl; } diff --git a/Swiften/Client/ClientXMLTracer.h b/Swiften/Client/ClientXMLTracer.h index 0752faa..67040c4 100644 --- a/Swiften/Client/ClientXMLTracer.h +++ b/Swiften/Client/ClientXMLTracer.h @@ -7,19 +7,20 @@ #pragma once #include <Swiften/Client/CoreClient.h> #include <Swiften/Client/XMLBeautifier.h> #include <Swiften/Base/SafeByteArray.h> namespace Swift { class ClientXMLTracer { public: - ClientXMLTracer(CoreClient* client); + ClientXMLTracer(CoreClient* client, bool bosh = false); ~ClientXMLTracer(); private: void printData(char direction, const SafeByteArray& data); void printLine(char c); private: XMLBeautifier *beautifier; + bool bosh; }; } diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp index 08f31a0..cef2b24 100644 --- a/Swiften/Client/CoreClient.cpp +++ b/Swiften/Client/CoreClient.cpp @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2011 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swiften/Client/CoreClient.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> @@ -14,18 +14,19 @@ #include <Swiften/Base/foreach.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Client/ClientSession.h> #include <Swiften/TLS/CertificateVerificationError.h> #include <Swiften/Network/ChainedConnector.h> #include <Swiften/Network/NetworkFactories.h> #include <Swiften/Network/ProxyProvider.h> #include <Swiften/TLS/PKCS12Certificate.h> #include <Swiften/Session/BasicSessionStream.h> +#include <Swiften/Session/BOSHSessionStream.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Client/ClientSessionStanzaChannel.h> #include <Swiften/Network/SOCKS5ProxiedConnectionFactory.h> #include <Swiften/Network/HTTPConnectProxiedConnectionFactory.h> #include <Swiften/Network/BOSHConnectionFactory.h> namespace Swift { CoreClient::CoreClient(const JID& jid, const SafeByteArray& password, NetworkFactories* networkFactories) : jid_(jid), password_(password), networkFactories(networkFactories), disconnectRequested_(false), certificateTrustChecker(NULL) { @@ -64,27 +65,64 @@ void CoreClient::connect(const std::string& host) { assert(!connector_); assert(proxyConnectionFactories.empty()); if(networkFactories->getProxyProvider()->getSOCKS5Proxy().isValid()) { proxyConnectionFactories.push_back(new SOCKS5ProxiedConnectionFactory(networkFactories->getConnectionFactory(), networkFactories->getProxyProvider()->getSOCKS5Proxy())); } if(networkFactories->getProxyProvider()->getHTTPConnectProxy().isValid()) { proxyConnectionFactories.push_back(new HTTPConnectProxiedConnectionFactory(networkFactories->getConnectionFactory(), networkFactories->getProxyProvider()->getHTTPConnectProxy())); } std::vector<ConnectionFactory*> connectionFactories(proxyConnectionFactories); - // connectionFactories.push_back(networkFactories->getConnectionFactory()); - connectionFactories.push_back(new BOSHConnectionFactory(networkFactories->getConnectionFactory())); + if (options.boshURL.empty()) { + connectionFactories.push_back(networkFactories->getConnectionFactory()); + connector_ = boost::make_shared<ChainedConnector>(host, networkFactories->getDomainNameResolver(), connectionFactories, networkFactories->getTimerFactory()); + connector_->onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1)); + connector_->setTimeoutMilliseconds(60*1000); + connector_->start(); + } + else { + /* Autodiscovery of which proxy works is largely ok with a TCP session, because this is a one-off. With BOSH + * it would be quite painful given that potentially every stanza could be sent on a new connection. + */ + //sessionStream_ = boost::make_shared<BOSHSessionStream>(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories->getConnectionFactory(), networkFactories->getXMLParserFactory(), networkFactories->getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories->getTLSContextFactory(), networkFactories->getTimerFactory(), networkFactories->getXMLParserFactory(), networkFactories->getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword); + sessionStream_ = boost::shared_ptr<BOSHSessionStream>(new BOSHSessionStream(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories->getConnectionFactory(), networkFactories->getXMLParserFactory(), networkFactories->getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories->getTLSContextFactory(), networkFactories->getTimerFactory(), networkFactories->getXMLParserFactory(), networkFactories->getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword)); + sessionStream_->onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1)); + sessionStream_->onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1)); + bindSessionToStream(); + } + +} - connector_ = boost::make_shared<ChainedConnector>(host, networkFactories->getDomainNameResolver(), connectionFactories, networkFactories->getTimerFactory()); - connector_->onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1)); - connector_->setTimeoutMilliseconds(60*1000); - connector_->start(); +void CoreClient::bindSessionToStream() { + session_ = ClientSession::create(jid_, sessionStream_); + session_->setCertificateTrustChecker(certificateTrustChecker); + session_->setUseStreamCompression(options.useStreamCompression); + session_->setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS); + switch(options.useTLS) { + case ClientOptions::UseTLSWhenAvailable: + session_->setUseTLS(ClientSession::UseTLSWhenAvailable); + break; + case ClientOptions::NeverUseTLS: + session_->setUseTLS(ClientSession::NeverUseTLS); + break; + case ClientOptions::RequireTLS: + session_->setUseTLS(ClientSession::RequireTLS); + break; + } + session_->setUseAcks(options.useAcks); + stanzaChannel_->setSession(session_); + session_->onFinished.connect(boost::bind(&CoreClient::handleSessionFinished, this, _1)); + session_->onNeedCredentials.connect(boost::bind(&CoreClient::handleNeedCredentials, this)); + session_->start(); } +/** + * Only called for TCP sessions. BOSH is handled inside the BOSHSessionStream. + */ void CoreClient::handleConnectorFinished(boost::shared_ptr<Connection> connection) { resetConnector(); if (!connection) { if (options.forgetPassword) { purgePassword(); } onDisconnected(disconnectRequested_ ? boost::optional<ClientError>() : boost::optional<ClientError>(ClientError::ConnectionError)); } else { @@ -93,38 +131,19 @@ void CoreClient::handleConnectorFinished(boost::shared_ptr<Connection> connectio assert(!sessionStream_); sessionStream_ = boost::make_shared<BasicSessionStream>(ClientStreamType, connection_, getPayloadParserFactories(), getPayloadSerializers(), networkFactories->getTLSContextFactory(), networkFactories->getTimerFactory(), networkFactories->getXMLParserFactory()); if (!certificate_.empty()) { sessionStream_->setTLSCertificate(PKCS12Certificate(certificate_, password_)); } sessionStream_->onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1)); sessionStream_->onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1)); - session_ = ClientSession::create(jid_, sessionStream_); - session_->setCertificateTrustChecker(certificateTrustChecker); - session_->setUseStreamCompression(options.useStreamCompression); - session_->setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS); - switch(options.useTLS) { - case ClientOptions::UseTLSWhenAvailable: - session_->setUseTLS(ClientSession::UseTLSWhenAvailable); - break; - case ClientOptions::NeverUseTLS: - session_->setUseTLS(ClientSession::NeverUseTLS); - break; - case ClientOptions::RequireTLS: - session_->setUseTLS(ClientSession::RequireTLS); - break; - } - session_->setUseAcks(options.useAcks); - stanzaChannel_->setSession(session_); - session_->onFinished.connect(boost::bind(&CoreClient::handleSessionFinished, this, _1)); - session_->onNeedCredentials.connect(boost::bind(&CoreClient::handleNeedCredentials, this)); - session_->start(); + bindSessionToStream(); } } void CoreClient::disconnect() { // FIXME: We should be able to do without this boolean. We just have to make sure we can tell the difference between // connector finishing without a connection due to an error or because of a disconnect. disconnectRequested_ = true; if (session_ && !session_->isFinished()) { session_->finish(); @@ -333,21 +352,26 @@ void CoreClient::resetConnector() { proxyConnectionFactories.clear(); } void CoreClient::resetSession() { session_->onFinished.disconnect(boost::bind(&CoreClient::handleSessionFinished, this, _1)); session_->onNeedCredentials.disconnect(boost::bind(&CoreClient::handleNeedCredentials, this)); sessionStream_->onDataRead.disconnect(boost::bind(&CoreClient::handleDataRead, this, _1)); sessionStream_->onDataWritten.disconnect(boost::bind(&CoreClient::handleDataWritten, this, _1)); - sessionStream_.reset(); - connection_->disconnect(); + if (connection_) { + connection_->disconnect(); + } + else if (boost::dynamic_pointer_cast<BOSHSessionStream>(sessionStream_)) { + sessionStream_->close(); + } + sessionStream_.reset(); connection_.reset(); } void CoreClient::forceReset() { if (connector_) { std::cerr << "Warning: Client not disconnected properly: Connector still active" << std::endl; resetConnector(); } if (sessionStream_ || connection_) { diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h index 3c089c1..c231fdc 100644 --- a/Swiften/Client/CoreClient.h +++ b/Swiften/Client/CoreClient.h @@ -23,19 +23,19 @@ namespace Swift { class Error; class IQRouter; class TLSContextFactory; class ConnectionFactory; class Connection; class TimerFactory; class ClientSession; class StanzaChannel; class Stanza; - class BasicSessionStream; + class SessionStream; class CertificateTrustChecker; class NetworkFactories; class ClientSessionStanzaChannel; /** * The central class for communicating with an XMPP server. * * This class is responsible for setting up the connection with the XMPP * server, authenticating, and initializing the session. @@ -201,31 +201,32 @@ namespace Swift { void handleStanzaChannelAvailableChanged(bool available); void handleSessionFinished(boost::shared_ptr<Error>); void handleNeedCredentials(); void handleDataRead(const SafeByteArray&); void handleDataWritten(const SafeByteArray&); void handlePresenceReceived(boost::shared_ptr<Presence>); void handleMessageReceived(boost::shared_ptr<Message>); void handleStanzaAcked(boost::shared_ptr<Stanza>); void purgePassword(); + void bindSessionToStream(); void resetConnector(); void resetSession(); void forceReset(); private: JID jid_; SafeByteArray password_; NetworkFactories* networkFactories; ClientSessionStanzaChannel* stanzaChannel_; IQRouter* iqRouter_; ClientOptions options; boost::shared_ptr<ChainedConnector> connector_; std::vector<ConnectionFactory*> proxyConnectionFactories; boost::shared_ptr<Connection> connection_; - boost::shared_ptr<BasicSessionStream> sessionStream_; + boost::shared_ptr<SessionStream> sessionStream_; boost::shared_ptr<ClientSession> session_; std::string certificate_; bool disconnectRequested_; CertificateTrustChecker* certificateTrustChecker; }; } diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp index e9d1b21..22db8fc 100644 --- a/Swiften/Client/UnitTest/ClientSessionTest.cpp +++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp @@ -397,18 +397,22 @@ class ClientSessionTest : public CppUnit::TestFixture { virtual Certificate::ref getPeerCertificate() const { return Certificate::ref(new SimpleCertificate()); } virtual boost::shared_ptr<CertificateVerificationError> getPeerCertificateVerificationError() const { return boost::shared_ptr<CertificateVerificationError>(); } + virtual bool supportsZLibCompression() { + return true; + } + virtual void addZLibCompression() { compressed = true; } virtual void setWhitespacePingEnabled(bool enabled) { whitespacePingEnabled = enabled; } virtual void resetXMPPParser() { |