|  | /* | 
|  | *  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 |