blob: e77a1d93497fe7d291ff042d1dd7cabbffa14562 [file] [log] [blame]
/*
* 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