| Index: webrtc/libjingle/xmpp/xmppengineimpl.cc
|
| diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.cc b/webrtc/libjingle/xmpp/xmppengineimpl.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e77a1d93497fe7d291ff042d1dd7cabbffa14562
|
| --- /dev/null
|
| +++ b/webrtc/libjingle/xmpp/xmppengineimpl.cc
|
| @@ -0,0 +1,446 @@
|
| +/*
|
| + * Copyright 2004 The WebRTC Project Authors. All rights reserved.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license
|
| + * that can be found in the LICENSE file in the root of the source
|
| + * tree. An additional intellectual property rights grant can be found
|
| + * in the file PATENTS. All contributing project authors may
|
| + * be found in the AUTHORS file in the root of the source tree.
|
| + */
|
| +
|
| +#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
|
| +
|
| +#include <algorithm>
|
| +#include <sstream>
|
| +#include <vector>
|
| +
|
| +#include "webrtc/libjingle/xmllite/xmlelement.h"
|
| +#include "webrtc/libjingle/xmllite/xmlprinter.h"
|
| +#include "webrtc/libjingle/xmpp/constants.h"
|
| +#include "webrtc/libjingle/xmpp/saslhandler.h"
|
| +#include "webrtc/libjingle/xmpp/xmpplogintask.h"
|
| +#include "webrtc/base/common.h"
|
| +
|
| +namespace buzz {
|
| +
|
| +XmppEngine* XmppEngine::Create() {
|
| + return new XmppEngineImpl();
|
| +}
|
| +
|
| +
|
| +XmppEngineImpl::XmppEngineImpl()
|
| + : stanza_parse_handler_(this),
|
| + stanza_parser_(&stanza_parse_handler_),
|
| + engine_entered_(0),
|
| + password_(),
|
| + requested_resource_(STR_EMPTY),
|
| + tls_option_(buzz::TLS_REQUIRED),
|
| + login_task_(new XmppLoginTask(this)),
|
| + next_id_(0),
|
| + state_(STATE_START),
|
| + encrypted_(false),
|
| + error_code_(ERROR_NONE),
|
| + subcode_(0),
|
| + stream_error_(),
|
| + raised_reset_(false),
|
| + output_handler_(NULL),
|
| + session_handler_(NULL),
|
| + iq_entries_(new IqEntryVector()),
|
| + sasl_handler_(),
|
| + output_(new std::stringstream()) {
|
| + for (int i = 0; i < HL_COUNT; i+= 1) {
|
| + stanza_handlers_[i].reset(new StanzaHandlerVector());
|
| + }
|
| +
|
| + // Add XMPP namespaces to XML namespaces stack.
|
| + xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
|
| + xmlns_stack_.AddXmlns("", "jabber:client");
|
| +}
|
| +
|
| +XmppEngineImpl::~XmppEngineImpl() {
|
| + DeleteIqCookies();
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetOutputHandler(
|
| + XmppOutputHandler* output_handler) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + output_handler_ = output_handler;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetSessionHandler(
|
| + XmppSessionHandler* session_handler) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + session_handler_ = session_handler;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::HandleInput(
|
| + const char* bytes, size_t len) {
|
| + if (state_ < STATE_OPENING || state_ > STATE_OPEN)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + EnterExit ee(this);
|
| +
|
| + // TODO: The return value of the xml parser is not checked.
|
| + stanza_parser_.Parse(bytes, len, false);
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
|
| + if (state_ != STATE_CLOSED) {
|
| + EnterExit ee(this);
|
| + // If told that connection closed and not already closed,
|
| + // then connection was unpexectedly dropped.
|
| + if (subcode) {
|
| + SignalError(ERROR_SOCKET, subcode);
|
| + } else {
|
| + SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode
|
| + }
|
| + }
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| + tls_option_ = use_tls;
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetTlsServer(
|
| + const std::string& tls_server_hostname,
|
| + const std::string& tls_server_domain) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + tls_server_hostname_ = tls_server_hostname;
|
| + tls_server_domain_= tls_server_domain;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +TlsOptions XmppEngineImpl::GetTls() {
|
| + return tls_option_;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + user_jid_ = jid;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +const Jid& XmppEngineImpl::GetUser() {
|
| + return user_jid_;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + sasl_handler_.reset(sasl_handler);
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SetRequestedResource(
|
| + const std::string& resource) {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + requested_resource_ = resource;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +const std::string& XmppEngineImpl::GetRequestedResource() {
|
| + return requested_resource_;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
|
| + XmppStanzaHandler* stanza_handler,
|
| + XmppEngine::HandlerLevel level) {
|
| + if (state_ == STATE_CLOSED)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + stanza_handlers_[level]->push_back(stanza_handler);
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
|
| + XmppStanzaHandler* stanza_handler) {
|
| + bool found = false;
|
| +
|
| + for (int level = 0; level < HL_COUNT; level += 1) {
|
| + StanzaHandlerVector::iterator new_end =
|
| + std::remove(stanza_handlers_[level]->begin(),
|
| + stanza_handlers_[level]->end(),
|
| + stanza_handler);
|
| +
|
| + if (new_end != stanza_handlers_[level]->end()) {
|
| + stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
|
| + found = true;
|
| + }
|
| + }
|
| +
|
| + if (!found)
|
| + return XMPP_RETURN_BADARGUMENT;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::Connect() {
|
| + if (state_ != STATE_START)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + EnterExit ee(this);
|
| +
|
| + // get the login task started
|
| + state_ = STATE_OPENING;
|
| + if (login_task_) {
|
| + login_task_->IncomingStanza(NULL, false);
|
| + if (login_task_->IsDone())
|
| + login_task_.reset();
|
| + }
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
|
| + if (state_ == STATE_CLOSED)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + EnterExit ee(this);
|
| +
|
| + if (login_task_) {
|
| + // still handshaking - then outbound stanzas are queued
|
| + login_task_->OutgoingStanza(element);
|
| + } else {
|
| + // handshake done - send straight through
|
| + InternalSendStanza(element);
|
| + }
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
|
| + if (state_ == STATE_CLOSED || login_task_)
|
| + return XMPP_RETURN_BADSTATE;
|
| +
|
| + EnterExit ee(this);
|
| +
|
| + (*output_) << text;
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +std::string XmppEngineImpl::NextId() {
|
| + std::stringstream ss;
|
| + ss << next_id_++;
|
| + return ss.str();
|
| +}
|
| +
|
| +XmppReturnStatus XmppEngineImpl::Disconnect() {
|
| + if (state_ != STATE_CLOSED) {
|
| + EnterExit ee(this);
|
| + if (state_ == STATE_OPEN)
|
| + *output_ << "</stream:stream>";
|
| + state_ = STATE_CLOSED;
|
| + }
|
| +
|
| + return XMPP_RETURN_OK;
|
| +}
|
| +
|
| +void XmppEngineImpl::IncomingStart(const XmlElement* start) {
|
| + if (HasError() || raised_reset_)
|
| + return;
|
| +
|
| + if (login_task_) {
|
| + // start-stream should go to login task
|
| + login_task_->IncomingStanza(start, true);
|
| + if (login_task_->IsDone())
|
| + login_task_.reset();
|
| + }
|
| + else {
|
| + // if not logging in, it's an error to see a start
|
| + SignalError(ERROR_XML, 0);
|
| + }
|
| +}
|
| +
|
| +void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
|
| + if (HasError() || raised_reset_)
|
| + return;
|
| +
|
| + if (stanza->Name() == QN_STREAM_ERROR) {
|
| + // Explicit XMPP stream error
|
| + SignalStreamError(stanza);
|
| + } else if (login_task_) {
|
| + // Handle login handshake
|
| + login_task_->IncomingStanza(stanza, false);
|
| + if (login_task_->IsDone())
|
| + login_task_.reset();
|
| + } else if (HandleIqResponse(stanza)) {
|
| + // iq is handled by above call
|
| + } else {
|
| + // give every "peek" handler a shot at all stanzas
|
| + for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
|
| + (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
|
| + }
|
| +
|
| + // give other handlers a shot in precedence order, stopping after handled
|
| + for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
|
| + for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
|
| + if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
|
| + return;
|
| + }
|
| + }
|
| +
|
| + // If nobody wants to handle a stanza then send back an error.
|
| + // Only do this for IQ stanzas as messages should probably just be dropped
|
| + // and presence stanzas should certainly be dropped.
|
| + std::string type = stanza->Attr(QN_TYPE);
|
| + if (stanza->Name() == QN_IQ &&
|
| + !(type == "error" || type == "result")) {
|
| + SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
|
| + }
|
| + }
|
| +}
|
| +
|
| +void XmppEngineImpl::IncomingEnd(bool isError) {
|
| + if (HasError() || raised_reset_)
|
| + return;
|
| +
|
| + SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
|
| +}
|
| +
|
| +void XmppEngineImpl::InternalSendStart(const std::string& to) {
|
| + std::string hostname = tls_server_hostname_;
|
| + if (hostname.empty())
|
| + hostname = to;
|
| +
|
| + // If not language is specified, the spec says use *
|
| + std::string lang = lang_;
|
| + if (lang.length() == 0)
|
| + lang = "*";
|
| +
|
| + // send stream-beginning
|
| + // note, we put a \r\n at tne end fo the first line to cause non-XMPP
|
| + // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
|
| + *output_ << "<stream:stream to=\"" << hostname << "\" "
|
| + << "xml:lang=\"" << lang << "\" "
|
| + << "version=\"1.0\" "
|
| + << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
|
| + << "xmlns=\"jabber:client\">\r\n";
|
| +}
|
| +
|
| +void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
|
| + // It should really never be necessary to set a FROM attribute on a stanza.
|
| + // It is implied by the bind on the stream and if you get it wrong
|
| + // (by flipping from/to on a message?) the server will close the stream.
|
| + ASSERT(!element->HasAttr(QN_FROM));
|
| +
|
| + XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
|
| +}
|
| +
|
| +std::string XmppEngineImpl::ChooseBestSaslMechanism(
|
| + const std::vector<std::string>& mechanisms, bool encrypted) {
|
| + return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
|
| +}
|
| +
|
| +SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
|
| + return sasl_handler_->CreateSaslMechanism(name);
|
| +}
|
| +
|
| +void XmppEngineImpl::SignalBound(const Jid& fullJid) {
|
| + if (state_ == STATE_OPENING) {
|
| + bound_jid_ = fullJid;
|
| + state_ = STATE_OPEN;
|
| + }
|
| +}
|
| +
|
| +void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
|
| + if (state_ != STATE_CLOSED) {
|
| + stream_error_.reset(new XmlElement(*stream_error));
|
| + SignalError(ERROR_STREAM, 0);
|
| + }
|
| +}
|
| +
|
| +void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
|
| + if (state_ != STATE_CLOSED) {
|
| + error_code_ = error_code;
|
| + subcode_ = sub_code;
|
| + state_ = STATE_CLOSED;
|
| + }
|
| +}
|
| +
|
| +bool XmppEngineImpl::HasError() {
|
| + return error_code_ != ERROR_NONE;
|
| +}
|
| +
|
| +void XmppEngineImpl::StartTls(const std::string& domain) {
|
| + if (output_handler_) {
|
| + // As substitute for the real (login jid's) domain, we permit
|
| + // verifying a tls_server_domain_ instead, if one was passed.
|
| + // This allows us to avoid running a proxy that needs to handle
|
| + // valuable certificates.
|
| + output_handler_->StartTls(
|
| + tls_server_domain_.empty() ? domain : tls_server_domain_);
|
| + encrypted_ = true;
|
| + }
|
| +}
|
| +
|
| +XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
|
| + : engine_(engine),
|
| + state_(engine->state_) {
|
| + engine->engine_entered_ += 1;
|
| +}
|
| +
|
| +XmppEngineImpl::EnterExit::~EnterExit() {
|
| + XmppEngineImpl* engine = engine_;
|
| +
|
| + engine->engine_entered_ -= 1;
|
| +
|
| + bool closing = (engine->state_ != state_ &&
|
| + engine->state_ == STATE_CLOSED);
|
| + bool flushing = closing || (engine->engine_entered_ == 0);
|
| +
|
| + if (engine->output_handler_ && flushing) {
|
| + std::string output = engine->output_->str();
|
| + if (output.length() > 0)
|
| + engine->output_handler_->WriteOutput(output.c_str(), output.length());
|
| + engine->output_->str("");
|
| +
|
| + if (closing) {
|
| + engine->output_handler_->CloseConnection();
|
| + engine->output_handler_ = 0;
|
| + }
|
| + }
|
| +
|
| + if (engine->engine_entered_)
|
| + return;
|
| +
|
| + if (engine->raised_reset_) {
|
| + engine->stanza_parser_.Reset();
|
| + engine->raised_reset_ = false;
|
| + }
|
| +
|
| + if (engine->session_handler_) {
|
| + if (engine->state_ != state_)
|
| + engine->session_handler_->OnStateChange(engine->state_);
|
| + // Note: Handling of OnStateChange(CLOSED) should allow for the
|
| + // deletion of the engine, so no members should be accessed
|
| + // after this line.
|
| + }
|
| +}
|
| +
|
| +} // namespace buzz
|
|
|